LASSO regression using thresholded NMF corr genes?

Regress on NMF score

Pseudobulk Expression

pseudobulk_vst <- readRDS("../BALLbulk_Deconvolution/BALL_89pt_pseudobulk_vst.rds")
pseudobulk_vst
An object of class Seurat 
29105 features across 89 samples within 1 assay 
Active assay: RNA (29105 features, 0 variable features)
NMF_ptscores <- read_csv('../CompositionAnalysis/BALL_Composition_DevState_NMFscores.csv') %>% 
  left_join(read_csv('scBALL_IDconversion.csv')) %>%
  select(ID_Bulk = ID, contains('NMF')) %>%
  pivot_longer(-ID_Bulk, names_to = 'NMF', values_to = 'NMFscore') %>% 
  left_join(pseudobulk_vst@meta.data %>% select(ID_Bulk) %>% rownames_to_column('ID_scRNA') ) %>% 
  mutate(Lineage = NMF %>% str_replace('.*_',''), NMF = NMF %>% str_replace('_.*','')) %>% 
  select(Patient = ID_scRNA, NMF, Lineage, NMFscore) 

── Column specification ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
cols(
  TB = col_character(),
  NMF1_ProB = col_double(),
  NMF2_EarlyLymphoid = col_double(),
  NMF3_Erythroid = col_double(),
  NMF4_PreB = col_double(),
  NMF5_MatureB = col_double(),
  NMF6_MyeloidProg = col_double(),
  NMF7_pDC = col_double(),
  NMF8_HSCMPPLMPP = col_double(),
  NMF9_Monocyte = col_double(),
  NMF10_TNK = col_double()
)

── Column specification ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
cols(
  Patient = col_character(),
  TB = col_character(),
  ID = col_character(),
  Sample_ID = col_character()
)
Joining with `by = join_by(TB)`Joining with `by = join_by(ID_Bulk)`
NMF_ptscores
NMFcorr <- read_csv('NMF_GeneCorr_Thresholding.csv') %>% 
  filter(threshold == 'pass', qvalue < 0.01) %>% arrange(qvalue) %>% arrange(NMF)

── Column specification ───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
cols(
  NMF = col_character(),
  Gene = col_character(),
  pearson = col_double(),
  pvalue = col_double(),
  qvalue = col_double(),
  pos_K1_threshold = col_double(),
  neg_K1_threshold = col_double(),
  threshold = col_character()
)
NMFcorr

Nested Cross Validation by Feature set

Define feature space - load markers

LinDE_FDR01_genes <- read_csv('BALL_DEresults_NMF_Lineage.csv') %>% filter(padj < 0.01, stat > 0) %>% pull(Gene) %>% unique()

── Column specification ───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
cols(
  Gene = col_character(),
  baseMean = col_double(),
  log2FoldChange = col_double(),
  lfcSE = col_double(),
  stat = col_double(),
  pvalue = col_double(),
  padj = col_double(),
  Lineage = col_character()
)
LinDE_FDR05_genes <- read_csv('BALL_DEresults_NMF_Lineage.csv') %>% filter(padj < 0.05, stat > 0) %>% pull(Gene) %>% unique()

── Column specification ───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
cols(
  Gene = col_character(),
  baseMean = col_double(),
  log2FoldChange = col_double(),
  lfcSE = col_double(),
  stat = col_double(),
  pvalue = col_double(),
  padj = col_double(),
  Lineage = col_character()
)
BDevDE_FDR01_genes <- read_csv('../BDevelopment_Characterization/BDevelopment_CellType_DEresults.csv') %>% 
  filter(padj < 0.01, stat > 0) %>% pull(Gene) %>% unique()

── Column specification ───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
cols(
  Gene = col_character(),
  baseMean = col_double(),
  log2FoldChange = col_double(),
  lfcSE = col_double(),
  stat = col_double(),
  pvalue = col_double(),
  padj = col_double(),
  CellType = col_character()
)
BDevDE_FDR05_genes <- read_csv('../BDevelopment_Characterization/BDevelopment_CellType_DEresults.csv') %>% 
  filter(padj < 0.05, stat > 0) %>% pull(Gene) %>% unique()

── Column specification ───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
cols(
  Gene = col_character(),
  baseMean = col_double(),
  log2FoldChange = col_double(),
  lfcSE = col_double(),
  stat = col_double(),
  pvalue = col_double(),
  padj = col_double(),
  CellType = col_character()
)
NMFcorr <- NMFcorr %>% mutate(LinDE_FDR05 = Gene %in% LinDE_FDR05_genes, 
                              LinDE_FDR01 = Gene %in% LinDE_FDR01_genes, 
                              BDevDE_FDR01 = Gene %in% BDevDE_FDR01_genes, 
                              BDevDE_FDR05 = Gene %in% BDevDE_FDR05_genes)
NMFcorr
evaluate_model <- function(model, x_val, anno_val, lambda, 
                           feature_name, iteration, foldname){

  # Create score classification with survival and get covariates
  pred_y <- predict(model, x_val, s = lambda) %>% data.frame()
  colnames(pred_y) <- 'PredScore'
  pred_y <- pred_y %>% rownames_to_column('Patient') %>% 
    # add anno to get covariates
    left_join(anno_val, by = 'Patient')
  
  # Calculate correlation in validation set
  pearson <- cor(pred_y$PredScore, pred_y$NMFscore, method = 'pearson')
  spearman <- cor(pred_y$PredScore, pred_y$NMFscore, method = 'spearman')
  
  # Summary Metrics
  summary_metrics <- data.frame(
    'model_id' = paste0(feature_name, '_iter', iteration, '_', foldname),
    'lambda' = lambda,
    'model_size' = sum(coef(model, s = lambda)!=0),
    'pearson' = pearson,
    'spearman' = spearman,
    'features' = feature_name,
    'iteration' = iteration,
    'foldname' = foldname
  )
  return(summary_metrics)
}
gridsearch_lasso <- function(expr_train, expr_val, anno_train, anno_val, features, feature_name,
                             iteration, foldname, summary_metrics){
  
  # Filter expr matrix for feature set
  x_train <- expr_train[, colnames(expr_train) %in% features]
  x_val <- expr_val[, colnames(expr_val) %in% features]

  # Train LASSO 
  model <- train_LASSO(x_train, anno_train)

  # Get summary metrics for lambda.min and lambda.1se
  for(lambda in c('lambda.min', 'lambda.1se')){
    summary_metrics <- summary_metrics %>% rbind(
      evaluate_model(model = model, x_val = x_val, anno_val = anno_val, lambda = lambda, 
                     feature_name = feature_name, iteration = iteration, foldname = foldname))
  }
  
  return(summary_metrics)
}
nestedCV_regression <- function(train_anno, train_expr, iteration, feature_sets, summary_metrics){
  # set up random seed and shuffle data 
  set.seed(iteration)
  train_anno <- train_anno[sample(nrow(train_anno)),]
  train_expr <- train_expr[sample(nrow(train_expr)),]
  
  ## 5-fold outer cross validation
  folds <- rsample::vfold_cv(train_anno, 5)
  for(outer_cv in 1:5){
    # fold ID
    foldname <- folds$id[[outer_cv]]
    # get anno splits
    anno_train <- analysis(folds$splits[[outer_cv]])
    anno_val <- assessment(folds$splits[[outer_cv]])
    # get expr splits
    expr_train <- train_expr[anno_train$Patient,]
    expr_val <- train_expr[anno_val$Patient,]
    
    # Iterate through feature set and run gridsearch to train survival functions
    for(feature_name in names(feature_sets)){
      # get feature list
      features <- feature_sets[[feature_name]]
      # run gridsearch and get results
      summary_metrics <- gridsearch_lasso(expr_train = expr_train, expr_val = expr_val, anno_train = anno_train, anno_val = anno_val, 
                                  features = features, feature_name = feature_name, iteration = iteration, foldname = foldname,
                                  summary_metrics = summary_metrics)
    }
  }
  return(summary_metrics)
}
library(tidymodels)
library(glmnet)

output <- data.frame()
train_x <- pseudobulk_vst@assays$RNA@data[,unique(NMF_ptscores$Patient)] %>% data.matrix() %>% t()

for(NMFcomp in c('NMF1', 'NMF2', 'NMF3', 'NMF4', 'NMF5', 'NMF6', 'NMF7', 'NMF8', 'NMF9', 'NMF10')){
  
  print(paste0('NMF Component: ', NMFcomp))
  
  temp_output <- data.frame()
  
  train_y <- NMF_ptscores %>% filter(NMF == NMFcomp) %>% select(Patient, NMFscore)
  featurespace <- list('PosCorr_LinDE_FDR05' = NMFcorr %>% filter(NMF == NMFcomp, LinDE_FDR05 == TRUE, pearson > 0) %>% pull(Gene), 
                       'PosCorr_LinDE_FDR01' = NMFcorr %>% filter(NMF == NMFcomp, LinDE_FDR01 == TRUE, pearson > 0) %>% pull(Gene), 
                       'PosCorr_LinDE_BDevDE_FDR05' = NMFcorr %>% filter(NMF == NMFcomp, LinDE_FDR05 == TRUE, BDevDE_FDR05 == TRUE, pearson > 0) %>% pull(Gene), 
                       'PosCorr_LinDE_BDevDE_FDR01' = NMFcorr %>% filter(NMF == NMFcomp, LinDE_FDR01 == TRUE, BDevDE_FDR01 == TRUE, pearson > 0) %>% pull(Gene), 
                       'AnyCorr_LinDE_FDR05' = NMFcorr %>% filter(NMF == NMFcomp, LinDE_FDR05 == TRUE) %>% pull(Gene),
                       'AnyCorr_LinDE_FDR01' = NMFcorr %>% filter(NMF == NMFcomp, LinDE_FDR01 == TRUE) %>% pull(Gene),
                       'AnyCorr_LinDE_BDevDE_FDR05' = NMFcorr %>% filter(NMF == NMFcomp, LinDE_FDR05 == TRUE, BDevDE_FDR05 == TRUE) %>% pull(Gene), 
                       'AnyCorr_LinDE_BDevDE_FDR01' = NMFcorr %>% filter(NMF == NMFcomp, LinDE_FDR01 == TRUE, BDevDE_FDR01 == TRUE) %>% pull(Gene)
  )
  
  for(iteration in 1:10){
    print(paste0('iteration ', iteration))
    temp_output <- nestedCV_regression(train_anno = train_y, train_expr = train_x, iteration = iteration, feature_sets = featurespace, 
                                       summary_metrics = temp_output) 
  }
  ## annotate and add to final output
  output <- bind_rows(output, temp_output %>% mutate(NMF = NMFcomp))
}

output %>% write_csv('RepNestedCV_results_NMFregression.csv')

5-Fold cross validation with 10 repeats within the pseudobulk to estimate the best parameters and get a gestalt of the overall accuracy After choosing the best combination of parameters we will test on the bulk RNA-seq dataset.

output <- read_csv('RepNestedCV_results_NMFregression.csv') 

── Column specification ───────────────────────────────────────────────────────────────────────────────────────────────────────
cols(
  model_id = col_character(),
  lambda = col_character(),
  model_size = col_double(),
  pearson = col_double(),
  spearman = col_double(),
  features = col_character(),
  iteration = col_double(),
  foldname = col_character(),
  NMF = col_character()
)
output %>% pull(features) %>% table()
.
AnyCorr_LinDE_BDevDE_FDR01 AnyCorr_LinDE_BDevDE_FDR05        AnyCorr_LinDE_FDR01        AnyCorr_LinDE_FDR05 
                      1000                       1000                       1000                       1000 
PosCorr_LinDE_BDevDE_FDR01 PosCorr_LinDE_BDevDE_FDR05        PosCorr_LinDE_FDR01        PosCorr_LinDE_FDR05 
                      1000                       1000                       1000                       1000 
output %>% 
  mutate(NMF = factor(NMF, levels = c('NMF1', 'NMF2', 'NMF3', 'NMF4', 'NMF5', 'NMF6', 'NMF7', 'NMF8', 'NMF9', 'NMF10'))) %>% 
  ggplot(aes(x = reorder(features, -model_size), y = model_size, fill = lambda)) + 
  geom_hline(yintercept = 10, lty = 2) + geom_hline(yintercept = 20, lty = 2) + 
  geom_hline(yintercept = 30, lty = 2) + geom_hline(yintercept = 40, lty = 2) + 
  geom_boxplot(outlier.size = 0.8) + ggbeeswarm::geom_quasirandom(dodge.width = 0.7, size = 0.2, alpha = 0.7) + 
  facet_wrap(.~NMF, ncol = 6) + theme_pubr() + 
  theme(axis.text.x = element_text(angle = 90, hjust = 1, vjust = 0.5)) + 
  stat_compare_means(label = 'p.signif')

NA
output %>% 
  mutate(NMF = factor(NMF, levels = c('NMF1', 'NMF2', 'NMF3', 'NMF4', 'NMF5', 'NMF6', 'NMF7', 'NMF8', 'NMF9', 'NMF10'))) %>% 
  ggplot(aes(x = reorder(features, -pearson), y = pearson, fill = lambda)) + 
  geom_boxplot(outlier.size = 0.8) + ggbeeswarm::geom_quasirandom(dodge.width = 0.7, size = 0.2, alpha = 0.7) + 
  facet_wrap(.~NMF, ncol = 6) + theme_pubr() + 
  theme(axis.text.x = element_text(angle = 90, hjust = 1, vjust = 0.5)) + 
  stat_compare_means(label = 'p.signif')

NA

In summary, using combination of positively and negatively correlated genes leads to better performance, particularly for: NMF1 (Pro-B) NMF5 (Erythroid) NMF11 (Naive T)

In terms of filtering of correlated genes; Lin DE BDev DE FDR < 0.01 as a filter consistently lead to the best performance.

Lambda choice of Minimum + 1SE resulted in model size reductions of nearly 20 genes without sacrificing performance by nested CV.

output_CVmedians <- output %>% filter(lambda == 'lambda.1se', features == 'AnyCorr_LinDE_BDevDE_FDR01') %>% #AnyCorr_LinDE_FDR01. AnyCorr_LinDE_BDevDE_FDR01
  select(NMF, model_size, pearson, spearman) %>% 
  group_by(NMF) %>% summarise_all(median) %>% arrange(pearson)

output_CVmedians 

Final Choice: Corr Threshold + Lineage DE FDR01; lambda min + 1SE

Train from both positively corr and any corr. If models from both approaches have negative coefficients, use any corr. Also make sure that the genes are present in the bulk RNAseq data

train_LASSO <- function(x_train, y_train, alpha = 1){
  
  train_y <- y_train$NMFscore
  
  # Perform Lasso regression with LOOCV 
  model <- cv.glmnet(x = x_train, y = train_y, nfold = dim(x_train)[1], family = 'gaussian', alpha = alpha, maxit=1000000, standardize=FALSE)
  #plot(model)

  return(model)
}
bulkRNAgenes <- data.table::fread("../BALLbulk_Deconvolution/BALL_bulkRNA_data/BALL_BulkRNAseq_subsetgene_rawcounts.txt")$Gene
bulkRNAgenes %>% length()
[1] 36110
set.seed(123)
model_list <- list()
# subset NMF corr with genes present in the bulk RNA data
NMFcorr <- NMFcorr %>% filter(Gene %in% bulkRNAgenes)

for(NMFcomp in c('NMF1', 'NMF2', 'NMF3', 'NMF4', 'NMF5', 'NMF6', 'NMF7', 'NMF8', 'NMF9', 'NMF10')){
  
  # Define y variable; NMF score
  train_y <- NMF_ptscores %>% filter(NMF == NMFcomp) %>% select(Patient, NMFscore)
  # Define feature space to train from
  feature_space <- NMFcorr %>% filter(NMF == NMFcomp, LinDE_FDR01 == TRUE, BDevDE_FDR01 == TRUE) %>% pull(Gene) #BDevDE_FDR01 == TRUE
  # subset training set
  train_x <- pseudobulk_vst@assays$RNA@data[feature_space, train_y$Patient] %>% data.matrix() %>% t()
  
  model <- train_LASSO(train_x, y_train = train_y)
  model_list[[NMFcomp]] <- model
}
Warning: Option grouped=FALSE enforced in cv.glmnet, since < 3 observations per foldWarning: Option grouped=FALSE enforced in cv.glmnet, since < 3 observations per foldWarning: Option grouped=FALSE enforced in cv.glmnet, since < 3 observations per foldWarning: Option grouped=FALSE enforced in cv.glmnet, since < 3 observations per foldWarning: Option grouped=FALSE enforced in cv.glmnet, since < 3 observations per foldWarning: Option grouped=FALSE enforced in cv.glmnet, since < 3 observations per foldWarning: Option grouped=FALSE enforced in cv.glmnet, since < 3 observations per foldWarning: Option grouped=FALSE enforced in cv.glmnet, since < 3 observations per foldWarning: Option grouped=FALSE enforced in cv.glmnet, since < 3 observations per foldWarning: Option grouped=FALSE enforced in cv.glmnet, since < 3 observations per fold
# model weights 
modelweights <- data.frame()

for(NMFcomp in c('NMF1', 'NMF2', 'NMF3', 'NMF4', 'NMF5', 'NMF6', 'NMF7', 'NMF8', 'NMF9', 'NMF10')){
  modelweights <- modelweights %>% bind_rows(
    model_list[[NMFcomp]] %>% coef(s = 'lambda.1se') %>% data.matrix() %>% 
      data.frame() %>% dplyr::rename(Weight = s1) %>% rownames_to_column('Gene') %>% 
      tail(-1) %>% filter(Weight != 0) %>% arrange(-Weight) %>% mutate(Model = NMFcomp)
  )
}

modelweights <- modelweights %>% select(Model, Gene, Weight)
modelweights %>% group_by(Model) %>% summarise(count = n())
# model weights 
modelweights <- data.frame()

for(NMFcomp in c('NMF1', 'NMF2', 'NMF3', 'NMF4', 'NMF5', 'NMF6', 'NMF7', 'NMF8', 'NMF9', 'NMF10')){
  modelweights <- modelweights %>% bind_rows(
    model_list[[NMFcomp]] %>% coef(s = 'lambda.1se') %>% data.matrix() %>% 
      data.frame() %>% dplyr::rename(Weight = s1) %>% rownames_to_column('Gene') %>% 
      tail(-1) %>% filter(Weight != 0) %>% arrange(-Weight) %>% mutate(Model = NMFcomp)
  )
}

modelweights <- modelweights %>% select(Model, Gene, Weight)
modelweights %>% group_by(Model) %>% summarise(count = n())
modelweights %>% 
  left_join(NMFconvert %>% dplyr::rename(Model = NMF)) %>%
  mutate(coefficient = ifelse(Weight > 0, 'Positive', 'Negative') %>% factor(levels = c('Positive', 'Negative'))) %>% 
  group_by(NMFnamed, coefficient) %>% summarise(count = n()) %>%
  ggplot(aes(x = NMFnamed, y = count, fill = coefficient)) + geom_col() + ggpubr::theme_pubr() + 
  ggsci::scale_fill_simpsons() + theme(axis.text.x = element_text(angle = 90, hjust = 1))
Joining with `by = join_by(Model)``summarise()` has grouped output by 'NMFnamed'. You can override using the `.groups` argument.

NMFnamed_levels <- c('HSC_MPP', 'Myeloid_Prog', 'Pre_pDC', 'Early_Lymphoid', 'Pro_B', 'Pre_B', 
                      'Mature_B', 'Erythroid', 'Monocyte', 'T_NK')

NMFconvert <- data.frame(
  'NMF' = c('NMF8', 'NMF6', 'NMF7', 'NMF2', 'NMF1', 'NMF4', 
            'NMF5', 'NMF3', 'NMF9', 'NMF10') %>% factor(),
  'NMFnamed' = NMFnamed_levels %>% factor(levels = NMFnamed_levels)
)

NMFconvert
modelweights <- modelweights %>% left_join(NMFconvert %>% dplyr::rename(Model = NMF)) %>% 
  arrange(Gene) %>% arrange(NMFnamed) %>% pivot_wider(id_cols=Gene, names_from=NMFnamed, values_from=Weight) %>% replace(is.na(.), 0) 
Joining with `by = join_by(Model)`
modelweights %>% write_csv("NMF_Lasso_ModelWeights.csv")
modelweights

Figure out NMF scoring and validate on pseudobulk

calculate_NMFscores = function(query, modelweights, scale = TRUE, sampleID = 'Patient'){
  
  # Check for overlap with model genes and query genes
  querygenes <- rownames(query)
  modelweights_missing <- sum(!(modelweights$Gene %in% querygenes))
  # check for missing genes
  if(modelweights_missing > 0){
    print(paste0('Warning: ', modelweights_missing, ' genes from NMF models are missing from query dataset'))
  }
  
  # filter model weights
  modelweights <- modelweights %>% filter(Gene %in% querygenes)
  modelweights_mat <- modelweights %>% column_to_rownames('Gene') %>% data.matrix()
  
  # multiply query by NMF lasso weights
  scored <- (t(query[modelweights$Gene,]) %*% modelweights_mat) %>% data.matrix() 
  if(scale == TRUE){
    scored <- scale(scored)
  }
  scored <- scored %>% as.data.frame() %>% rownames_to_column(sampleID) 
  
  return(scored)
}
modelweights <- read_csv("NMF_Lasso_ModelWeights.csv")

── Column specification ─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
cols(
  Gene = col_character(),
  HSC_MPP = col_double(),
  Myeloid_Prog = col_double(),
  Pre_pDC = col_double(),
  Early_Lymphoid = col_double(),
  Pro_B = col_double(),
  Pre_B = col_double(),
  Mature_B = col_double(),
  Erythroid = col_double(),
  Monocyte = col_double(),
  T_NK = col_double()
)
modelweights
pseudobulk_vst <- readRDS("../BALLbulk_Deconvolution/BALL_89pt_pseudobulk_vst.rds")
# calculate NMF scores from vst-normalized data
pseudobulk_vst_NMFscores <- calculate_NMFscores(pseudobulk_vst@assays$RNA@data, modelweights, scale = T, sampleID = 'Patient')
pseudobulk_vst_NMFscores[1:10,1:10]

Training results - pseudobulk

NMF_compare <- NMF_ptscores %>% 
  left_join(NMFconvert) %>% 
  left_join(pseudobulk_vst_NMFscores %>% pivot_longer(-Patient, names_to = 'NMFnamed', values_to = 'predNMF')) 
Joining with `by = join_by(NMF)`Joining with `by = join_by(Patient, NMFnamed)`
NMF_compare %>% 
  mutate(NMFnamed = factor(NMFnamed, levels = NMFnamed_levels)) %>% 
  ggplot(aes(x = predNMF, y = NMFscore)) + 
  geom_point() + geom_smooth(method = 'lm') + 
  facet_wrap(.~NMFnamed, scales = 'free') + 
  theme_pubr() + stat_cor()

True Bulk RNAseq “validation”

bulkRNA_counts <- data.table::fread("../BALLbulk_Deconvolution/BALL_bulkRNA_data/BALL_BulkRNAseq_subsetgene_rawcounts.txt") %>% 
  column_to_rownames('Gene') %>% data.matrix()
bulkRNA_counts[1:10,1:10]
          SJBALL020608_D1 SJBALL082_D SJINF074_D SJBALL209_D SJINF049_D SJALL050848_D1 SJBALL016239_D1 SJBALL205_D SJBALL016303_D1 SJBALL016244_D1
A1BG                  143         317        182         122         82            252            1090         298              78              51
A1BG-AS1              147         771        279         326        199            307             921         552             119             111
A1CF                    1           2          1           0          0              0               0           0               0               1
A2M                    11           8         20          75         15              4               4           8               1              88
A2M-AS1                 6         198          5         489         26             43              15          38              23              36
A2ML1                   0           0          2           0          1              2               0           0               0               0
A2ML1-AS1               0           1          0           0          0              0               0           0               0               0
A2ML1-AS2               0           0          0           0          0              0               1           0               0               0
A3GALT2                 2           1          2           0          2              2               0           0               5               0
A4GALT                 59          32         60          60         34              4              37          18               2              44
library(DESeq2)
bulkRNA_dds <- DESeqDataSetFromMatrix(bulkRNA_counts[rowSums(bulkRNA_counts) >= 10,], 
                       colData = data.frame('Patient' = colnames(bulkRNA_counts)) %>% column_to_rownames('Patient'), 
                       design = ~1)
bulkRNA_vst <- assay(vst(bulkRNA_dds))
rm(bulkRNA_counts, bulkRNA_dds)

bulkRNA_vst[1:10,1:10]
          SJBALL020608_D1 SJBALL082_D SJINF074_D SJBALL209_D SJINF049_D SJALL050848_D1 SJBALL016239_D1 SJBALL205_D
A1BG             7.140496    7.459732   7.069288    6.857087   6.789549       8.334186        9.654538    8.049401
A1BG-AS1         7.171814    8.579933   7.566172    8.013636   7.813683       8.592772        9.422367    8.855233
A1CF             4.393532    4.420817   4.350597    4.088563   4.088563       4.088563        4.088563    4.088563
A2M              5.082162    4.748766   5.231875    6.364534   5.376806       4.830286        4.673378    4.948642
A2M-AS1          4.828826    6.923254   4.671326    8.537353   5.748445       6.318600        5.201054    5.874671
A2ML1            4.088563    4.088563   4.458630    4.088563   4.431539       4.615895        4.088563    4.088563
A2ML1-AS1        4.088563    4.323761   4.088563    4.088563   4.088563       4.088563        4.088563    4.088563
A2ML1-AS2        4.088563    4.088563   4.088563    4.088563   4.088563       4.088563        4.382468    4.088563
A3GALT2          4.519059    4.323761   4.458630    4.088563   4.572475       4.615895        4.088563    4.088563
A4GALT           6.232613    5.377247   5.982178    6.159561   5.959306       4.830286        5.780731    5.356654
          SJBALL016303_D1 SJBALL016244_D1
A1BG             7.804691        7.006957
A1BG-AS1         8.341231        7.928454
A1CF             4.088563        4.568334
A2M              4.632465        7.641534
A2M-AS1          6.442545        6.636279
A2ML1            4.088563        4.088563
A2ML1-AS1        4.088563        4.088563
A2ML1-AS2        4.088563        4.088563
A3GALT2          5.278005        4.088563
A4GALT           4.853334        6.846260
# calculate NMF scores from vst-normalized data
bulkRNA_vst_NMFscores <- calculate_NMFscores(bulkRNA_vst, modelweights, scale = T, sampleID = 'Patient')
bulkRNA_vst_NMFscores[1:10,1:10]

Validation results - matched bulkRNAseq

NMF_ptscores_converted <- NMF_ptscores %>% 
  left_join(read_delim("../BALL_metadata_20230105.txt", delim = '\t') %>% select(Patient = Directory, Sample, ID, TB)) 

── Column specification ─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
cols(
  Sample = col_character(),
  ID = col_character(),
  Directory = col_character(),
  TB = col_character(),
  age = col_double(),
  sex = col_character(),
  diagnosis = col_character(),
  subdiagnosis = col_character(),
  location = col_character()
)
Joining with `by = join_by(Patient)`
NMF_ptscores_compareBulkRNA <- NMF_ptscores_converted %>% 
  left_join(NMFconvert) %>% 
  left_join(bulkRNA_vst_NMFscores %>% pivot_longer(-Patient, names_to = 'NMFnamed', values_to = 'predNMF') %>% 
              dplyr::rename(ID = Patient)) 
Joining with `by = join_by(NMF)`Joining with `by = join_by(ID, NMFnamed)`
NMF_ptscores_compareBulkRNA
NMF_ptscores_compareBulkRNA %>% 
  mutate(NMFnamed = factor(NMFnamed, NMFnamed_levels)) %>% 
  ggplot(aes(x = predNMF, y = NMFscore)) + 
  geom_point() + geom_smooth(method = 'lm') + 
  facet_wrap(.~NMFnamed, scales = 'free', ncol = 5) + 
  theme_pubr() + stat_cor() + 
  ylab('NMF Lineage Score (scRNA composition)') + xlab('Predicted NMF Score (matched bulk RNA-seq)')

NMF_ptscores_compareBulkRNA %>% 
  mutate(NMFnamed = factor(NMFnamed, NMFnamed_levels)) %>% 
  ggplot(aes(x = predNMF, y = NMFscore)) + 
  geom_point() + geom_smooth(method = 'lm') + 
  facet_wrap(.~NMFnamed, scales = 'free', ncol = 2) + 
  theme_pubr() + stat_cor() + 
  ylab('NMF Lineage Score (scRNA composition)') + xlab('Predicted NMF Score (matched bulk RNA-seq)')

Not bad!! Save bulk RNAseq NMF scores:

bulkRNA_vst_NMFscores %>% write_csv('Bulk2046_NMFregression_LineageScores.csv')
bulkRNA_vst_NMFscores
output %>% 
  left_join(NMFconvert) %>%
  #mutate(NMF = factor(NMF, levels = c('NMF1', 'NMF2', 'NMF3', 'NMF4', 'NMF5', 'NMF6', 'NMF7', 'NMF8', 'NMF9', 'NMF10'))) %>% 
  filter(features == 'AnyCorr_LinDE_BDevDE_FDR01', lambda == 'lambda.1se') %>% 
  ggplot(aes(x = NMFnamed, y = pearson, fill = NMFnamed)) + 
  theme_pubr(legend = 'none') + geom_hline(yintercept = 0.5, lty = 2) + geom_hline(yintercept = 0.75, lty = 2) + geom_hline(yintercept = 0.9, lty = 2) + 
  geom_boxplot(outlier.size = 0) + ggbeeswarm::geom_quasirandom(size = 1, alpha = 0.7) + 
  theme(axis.text.x = element_text(angle = 90, hjust = 1, vjust = 0.5)) 
Joining with `by = join_by(NMF)`

NA
output %>% 
  left_join(NMFconvert) %>%
  #mutate(NMF = factor(NMF, levels = c('NMF1', 'NMF2', 'NMF3', 'NMF4', 'NMF5', 'NMF6', 'NMF7', 'NMF8', 'NMF9', 'NMF10'))) %>% 
  filter(features == 'AnyCorr_LinDE_BDevDE_FDR01', lambda == 'lambda.1se') %>% 
  ggplot(aes(x = reorder(NMFnamed, -pearson), y = pearson, fill = NMFnamed)) + 
  theme_pubr(legend = 'none') + geom_hline(yintercept = 0.5, lty = 2) + geom_hline(yintercept = 0.75, lty = 2) + geom_hline(yintercept = 0.9, lty = 2) + 
  geom_boxplot(outlier.size = 0) + ggbeeswarm::geom_quasirandom(size = 1, alpha = 0.7) + 
  theme(axis.text.x = element_text(angle = 90, hjust = 1, vjust = 0.5)) 
  
NMF_ptscores_compareBulkRNA %>% drop_na()

quickly evaluate on cord blood sorted

cb_fractions = data.table::fread('../../../../../../../Dormancy/HSC_analysis/HSC_byOntogeny/Dicklab_sortedFractions_Batch4_CB_Hierarchy_vst.csv')
cb_fractions <- cb_fractions %>% column_to_rownames('Gene') %>% data.matrix()
cb_fractions %>% dim()
[1] 43165   107
CBfractions_vst_NMFscores <- calculate_NMFscores(cb_fractions, modelweights, scale = T, sampleID = 'Sample')
[1] "Warning: 1 genes from NMF models are missing from query dataset"
CBfractions_vst_NMFscores
CBfractions_vst_NMFscores %>% 
  pivot_longer(-Sample, names_to = 'NMFsig', values_to = 'Score') %>% 
  mutate(NMFsig = factor(NMFsig, levels = NMFnamed_levels), 
         Population = Sample %>% str_replace('.*CB_',''),
         Population = factor(Population, levels = c('HSC', 'MPP', 'LMPP', 'CMP', 'GMP', 'MLPII', 'EarlyProB', 'PreProB', 
                                                    'ProB', 'PreB', 'B', #'B1', 'B2', 'B3', 'B4', 'B5', 'B6', 
                                                    'T', 'NK', 'EryP', 'Mono', 'Gr'))) %>% 
  filter(Population != 'NA') %>% 
  ggplot(aes(x = Population, y = Score, fill = Population)) + 
  geom_boxplot() + geom_jitter() +
  theme_pubr(legend = 'none') + theme(axis.text.x = element_text(angle = 90, hjust = 1, vjust = 0.5)) +
  facet_wrap(.~NMFsig, scales = 'free', ncol = 3)

NA
CBfractions_vst_NMFscores %>% 
  pivot_longer(-Sample, names_to = 'NMFsig', values_to = 'Score') %>% 
  mutate(NMFsig = factor(NMFsig, levels = NMFnamed_levels), 
         Population = Sample %>% str_replace('.*CB_',''),
         Population = factor(Population, levels = c('HSC', 'MPP', 'LMPP', 'CMP', 'GMP', 'MLPII', 'EarlyProB', 'PreProB', 
                                                    'ProB', 'PreB', 'B', #'B1', 'B2', 'B3', 'B4', 'B5', 'B6', 
                                                    'T', 'NK', 'EryP', 'Mono', 'Gr'))) %>% 

Evaluate on Pharmacotypes

# require gene symbol column to be named "Gene"
rpkm_to_logTPM <- function(dat){
  # convert to TPM
  dat_TPM <- dat %>% 
    gather(-Gene, key = "Sample", value = "RPKM") %>%
    group_by(Sample) %>% 
    mutate(logTPM = log1p(RPKM / sum(RPKM) * 1000000)) %>% 
    select(-RPKM) %>% ungroup() %>% 
    spread(Sample, logTPM)
  
  return(dat_TPM)
}
pharmacotype_fpkm <- data.table::fread('pharmacotypes/pharmacotyping_ped_rnaseq_fpkm_ALLids_0823.csv') %>% select(-GeneID) %>% dplyr::rename(Gene = GeneName)
pharmacotype_fpkm
pharmacotype_logTPM <- pharmacotype_fpkm %>% rpkm_to_logTPM()
pharmacotype_logTPM <- pharmacotype_logTPM %>% column_to_rownames('Gene') %>% data.matrix()
pharmacotype_logTPM %>% dim()
pharmacotypes <- read_csv('pharmacotypes/ALL_invitro_pharmacotypes.csv')

── Column specification ─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
cols(
  .default = col_double(),
  `Patient ID` = col_character(),
  `Sample ID` = col_character(),
  Immunophenotype = col_character(),
  `Molecular subtype` = col_character(),
  Protocol = col_character(),
  `NCI risk` = col_character(),
  Sex = col_character(),
  `Population and ancestry` = col_character()
)
ℹ Use `spec()` for the full column specifications.
pharmacotypes
pharmacotypes_combined <- pharmacotypes %>% inner_join(pharmacotype_logTPM_scored) %>% filter(Immunophenotype == 'B')
Joining with `by = join_by(`Patient ID`)`
pharmacotypes_combined
CellType_Drug_Corr <- cor(x = select(pharmacotypes_combined, contains('normalized')), 
                          y = select(pharmacotypes_combined, HSC_MPP, Myeloid_Prog, Pre_pDC, Early_Lymphoid, 
                                     Pro_B, Pre_B, Mature_B, T_NK, Monocyte, Erythroid), use = 'pairwise.complete.obs', method = 'spearman')
CellType_Drug_Corr
                              HSC_MPP Myeloid_Prog      Pre_pDC Early_Lymphoid        Pro_B        Pre_B     Mature_B        T_NK     Monocyte   Erythroid
Asparaginase_normalized    0.06297156  0.146949854 -0.011408241    0.029215760 -0.176086803  0.043973855 -0.028931055 -0.02380685  0.074471547 -0.09803874
Bortezomib_normalized      0.10425820 -0.156163247 -0.009683717   -0.099246543 -0.032780758  0.015565485  0.091815461  0.05255612 -0.164868343  0.01398804
CHZ868_normalized         -0.23378972  0.143426764 -0.365216511   -0.128265675 -0.088951192 -0.322730680 -0.135304022  0.24603521  0.271579908  0.18831668
Cytarabine_normalized      0.06574424  0.037662561  0.025593659    0.157748568 -0.001227337 -0.176277524 -0.068655134 -0.08831712 -0.092632208 -0.03560725
Dasatinib_normalized       0.08106764  0.021310534  0.071770981    0.104709385  0.023748006 -0.170917984 -0.001895330 -0.05798105 -0.090192980  0.09435230
Daunorubicin_normalized    0.11223808  0.073212138  0.093254818   -0.055860016 -0.118538604 -0.127861052  0.094619932  0.01121027 -0.009758818 -0.05619371
Dexamethasone_normalized   0.01083389  0.004212854  0.099537893   -0.070950751 -0.153948693 -0.182048537  0.134455023  0.13229506  0.101970249  0.05366476
Ibrutinib_normalized       0.07157877  0.027072835 -0.130261628    0.005968476 -0.154157001 -0.021891568  0.118207788  0.14282596  0.031242657  0.08075572
Mercaptopurine_normalized  0.11074588  0.111776761  0.032717251    0.054946846 -0.152427880 -0.130912251  0.030001120 -0.02326335  0.004634170  0.01969312
Nelarabine_normalized      0.03055041 -0.187116251  0.161731174    0.122817203  0.071404728 -0.122710150  0.004737120 -0.01666021 -0.102182628  0.19340565
Panobinostat_normalized   -0.06571385  0.115933478 -0.046417098   -0.028606394 -0.146441137 -0.336305426  0.022880745  0.05598899  0.152997225  0.20260495
Prednisolone_normalized    0.20934634  0.059269053  0.250990414    0.123834369 -0.092154364 -0.036719634 -0.029667418 -0.06413731 -0.040582133 -0.04349867
Ruxolitinib_normalized     0.11996152  0.010825715  0.012268038   -0.096602513  0.022206807  0.083861990 -0.014207715 -0.07556449 -0.080703799  0.02473502
Thioguanine_normalized     0.27711897  0.066801038  0.179078618    0.218979572 -0.003125489  0.005995744 -0.116854086 -0.14908658 -0.185575536 -0.08553664
Trametinib_normalized     -0.24754694 -0.137116487 -0.336145912   -0.033905251  0.063186014 -0.096718034 -0.028086933  0.16363125  0.029472491  0.25015189
Venetoclax_normalized     -0.18051023  0.057766556 -0.409731802   -0.026677120 -0.063144189 -0.055577985  0.001113826  0.20009403  0.239062327  0.07513834
Vincristine_normalized     0.04590763  0.040889885  0.102042820    0.097484450  0.097688176  0.006290842 -0.058296676 -0.07866391 -0.134378552 -0.06877864
Vorinostat_normalized     -0.04506071 -0.040089676 -0.087037205    0.085285161 -0.034263351 -0.272137053  0.025057343  0.05707103  0.027493618  0.10202289
CellType_Drug_Corr
library(corrplot)
CellType_Drug_Corr %>% t() %>% corrplot()

Jae Kim B-ALL Ph+

Kim_PhALL_samples <- list.files('subtype_subcluster/Kim2023_Ph_BALL/RNAseq_rawcounts/')
Kim_PhALL_samples
 [1] "JAMLR_0003_nn_P_count_sub.txt" "JAMLR_0004_nn_P_count_sub.txt" "JAMLR_0005_nn_P_count_sub.txt" "JAMLR_0006_nn_P_count_sub.txt"
 [5] "JAMLR_0007_nn_P_count_sub.txt" "JAMLR_0008_nn_P_count_sub.txt" "JAMLR_0009_nn_P_count_sub.txt" "JAMLR_0010_nn_P_count_sub.txt"
 [9] "JAMLR_0011_nn_P_count_sub.txt" "JAMLR_0012_nn_P_count_sub.txt" "JAMLR_0013_nn_P_count_sub.txt" "JAMLR_0014_nn_M_count_sub.txt"
[13] "JAMLR_0014_nn_P_count_sub.txt" "JAMLR_0015_nn_P_count_sub.txt" "JAMLR_0016_nn_P_count_sub.txt" "JAMLR_0017_nn_M_count_sub.txt"
[17] "JAMLR_0017_nn_P_count_sub.txt" "JAMLR_0018_nn_P_count_sub.txt" "JAMLR_0019_nn_M_count_sub.txt" "JAMLR_0019_nn_P_count_sub.txt"
[21] "JAMLR_0020_nn_P_count_sub.txt" "JAMLR_0021_nn_P_count_sub.txt" "JAMLR_0022_nn_P_count_sub.txt" "JAMLR_0023_nn_P_count_sub.txt"
[25] "JAMLR_0024_nn_P_count_sub.txt" "JAMLR_0025_nn_P_count_sub.txt" "JAMLR_0026_nn_P_count_sub.txt" "JAMLR_0027_nn_P_count_sub.txt"
[29] "JAMLR_0028_nn_P_count_sub.txt" "JAMLR_0029_nn_P_count_sub.txt" "JAMLR_0030_nn_P_count_sub.txt" "JAMLR_0031_nn_P_count_sub.txt"
[33] "JAMLR_0032_nn_P_count_sub.txt" "JAMLR_0033_nn_P_count_sub.txt" "JAMLR_0034_nn_P_count_sub.txt" "JAMLR_0035_nn_P_count_sub.txt"
[37] "JAMLR_0036_nn_P_count_sub.txt" "JAMLR_0037_nn_P_count_sub.txt" "JAMLR_0038_nn_P_count_sub.txt" "JAMLR_0039_nn_P_count_sub.txt"
[41] "JAMLR_0040_nn_P_count_sub.txt" "JAMLR_0041_nn_P_count_sub.txt" "JAMLR_0042_nn_P_count_sub.txt" "JAMLR_0043_nn_P_count_sub.txt"
[45] "JAMLR_0044_nn_P_count_sub.txt" "JAMLR_0045_nn_P_count_sub.txt" "JAMLR_0046_nn_P_count_sub.txt" "JAMLR_0047_nn_P_count_sub.txt"
[49] "JAMLR_0048_nn_P_count_sub.txt" "JAMLR_0049_nn_P_count_sub.txt" "JAMLR_0050_nn_P_count_sub.txt" "JAMLR_0051_nn_P_count_sub.txt"
[53] "JAMLR_0052_nn_P_count_sub.txt" "JAMLR_0053_nn_P_count_sub.txt" "JAMLR_0054_nn_P_count_sub.txt" "JAMLR_0055_nn_M_count_sub.txt"
[57] "JAMLR_0055_nn_P_count_sub.txt"
Kim_PhALL_anno <- read_csv('subtype_subcluster/Kim2023_Ph_BALL/Ph_BALL_ClinicalAnno.csv')

── Column specification ───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
cols(
  .default = col_character(),
  Cohort = col_double(),
  Age_at_dx = col_double(),
  Age_at_dx_rounded = col_double(),
  OS = col_double(),
  alive = col_double(),
  RFS = col_double(),
  relapse_or_death = col_double(),
  OS_BMT_censored = col_double(),
  alive_BMT_censored = col_double(),
  WBC = col_double()
)
ℹ Use `spec()` for the full column specifications.
Kim_PhALL_anno
kim_phALL_counts <- data.table::fread(paste0('subtype_subcluster/Kim2023_Ph_BALL/RNAseq_rawcounts/', Kim_PhALL_samples[1])) 
colnames(kim_phALL_counts) <- c('ENSG', Kim_PhALL_samples[1] %>% str_replace('_count_sub.txt',''))

for(ph_samp in Kim_PhALL_samples[-1]){
  print(ph_samp)
  # Load file 
  temp <- data.table::fread(paste0('subtype_subcluster/Kim2023_Ph_BALL/RNAseq_rawcounts/', ph_samp))
  colnames(temp) <- c('ENSG', ph_samp %>% str_replace('_count_sub.txt','')) 
  # merge
  kim_phALL_counts <- kim_phALL_counts %>% left_join(temp)
}
[1] "JAMLR_0004_nn_P_count_sub.txt"
Joining with `by = join_by(ENSG)`
[1] "JAMLR_0005_nn_P_count_sub.txt"
Joining with `by = join_by(ENSG)`
[1] "JAMLR_0006_nn_P_count_sub.txt"
Joining with `by = join_by(ENSG)`
[1] "JAMLR_0007_nn_P_count_sub.txt"
Joining with `by = join_by(ENSG)`
[1] "JAMLR_0008_nn_P_count_sub.txt"
Joining with `by = join_by(ENSG)`
[1] "JAMLR_0009_nn_P_count_sub.txt"
Joining with `by = join_by(ENSG)`
[1] "JAMLR_0010_nn_P_count_sub.txt"
Joining with `by = join_by(ENSG)`
[1] "JAMLR_0011_nn_P_count_sub.txt"
Joining with `by = join_by(ENSG)`
[1] "JAMLR_0012_nn_P_count_sub.txt"
Joining with `by = join_by(ENSG)`
[1] "JAMLR_0013_nn_P_count_sub.txt"
Joining with `by = join_by(ENSG)`
[1] "JAMLR_0014_nn_M_count_sub.txt"
Joining with `by = join_by(ENSG)`
[1] "JAMLR_0014_nn_P_count_sub.txt"
Joining with `by = join_by(ENSG)`
[1] "JAMLR_0015_nn_P_count_sub.txt"
Joining with `by = join_by(ENSG)`
[1] "JAMLR_0016_nn_P_count_sub.txt"
Joining with `by = join_by(ENSG)`
[1] "JAMLR_0017_nn_M_count_sub.txt"
Joining with `by = join_by(ENSG)`
[1] "JAMLR_0017_nn_P_count_sub.txt"
Joining with `by = join_by(ENSG)`
[1] "JAMLR_0018_nn_P_count_sub.txt"
Joining with `by = join_by(ENSG)`
[1] "JAMLR_0019_nn_M_count_sub.txt"
Joining with `by = join_by(ENSG)`
[1] "JAMLR_0019_nn_P_count_sub.txt"
Joining with `by = join_by(ENSG)`
[1] "JAMLR_0020_nn_P_count_sub.txt"
Joining with `by = join_by(ENSG)`
[1] "JAMLR_0021_nn_P_count_sub.txt"
Joining with `by = join_by(ENSG)`
[1] "JAMLR_0022_nn_P_count_sub.txt"
Joining with `by = join_by(ENSG)`
[1] "JAMLR_0023_nn_P_count_sub.txt"
Joining with `by = join_by(ENSG)`
[1] "JAMLR_0024_nn_P_count_sub.txt"
Joining with `by = join_by(ENSG)`
[1] "JAMLR_0025_nn_P_count_sub.txt"
Joining with `by = join_by(ENSG)`
[1] "JAMLR_0026_nn_P_count_sub.txt"
Joining with `by = join_by(ENSG)`
[1] "JAMLR_0027_nn_P_count_sub.txt"
Joining with `by = join_by(ENSG)`
[1] "JAMLR_0028_nn_P_count_sub.txt"
Joining with `by = join_by(ENSG)`
[1] "JAMLR_0029_nn_P_count_sub.txt"
Joining with `by = join_by(ENSG)`
[1] "JAMLR_0030_nn_P_count_sub.txt"
Joining with `by = join_by(ENSG)`
[1] "JAMLR_0031_nn_P_count_sub.txt"
Joining with `by = join_by(ENSG)`
[1] "JAMLR_0032_nn_P_count_sub.txt"
Joining with `by = join_by(ENSG)`
[1] "JAMLR_0033_nn_P_count_sub.txt"
Joining with `by = join_by(ENSG)`
[1] "JAMLR_0034_nn_P_count_sub.txt"
Joining with `by = join_by(ENSG)`
[1] "JAMLR_0035_nn_P_count_sub.txt"
Joining with `by = join_by(ENSG)`
[1] "JAMLR_0036_nn_P_count_sub.txt"
Joining with `by = join_by(ENSG)`
[1] "JAMLR_0037_nn_P_count_sub.txt"
Joining with `by = join_by(ENSG)`
[1] "JAMLR_0038_nn_P_count_sub.txt"
Joining with `by = join_by(ENSG)`
[1] "JAMLR_0039_nn_P_count_sub.txt"
Joining with `by = join_by(ENSG)`
[1] "JAMLR_0040_nn_P_count_sub.txt"
Joining with `by = join_by(ENSG)`
[1] "JAMLR_0041_nn_P_count_sub.txt"
Joining with `by = join_by(ENSG)`
[1] "JAMLR_0042_nn_P_count_sub.txt"
Joining with `by = join_by(ENSG)`
[1] "JAMLR_0043_nn_P_count_sub.txt"
Joining with `by = join_by(ENSG)`
[1] "JAMLR_0044_nn_P_count_sub.txt"
Joining with `by = join_by(ENSG)`
[1] "JAMLR_0045_nn_P_count_sub.txt"
Joining with `by = join_by(ENSG)`
[1] "JAMLR_0046_nn_P_count_sub.txt"
Joining with `by = join_by(ENSG)`
[1] "JAMLR_0047_nn_P_count_sub.txt"
Joining with `by = join_by(ENSG)`
[1] "JAMLR_0048_nn_P_count_sub.txt"
Joining with `by = join_by(ENSG)`
[1] "JAMLR_0049_nn_P_count_sub.txt"
Joining with `by = join_by(ENSG)`
[1] "JAMLR_0050_nn_P_count_sub.txt"
Joining with `by = join_by(ENSG)`
[1] "JAMLR_0051_nn_P_count_sub.txt"
Joining with `by = join_by(ENSG)`
[1] "JAMLR_0052_nn_P_count_sub.txt"
Joining with `by = join_by(ENSG)`
[1] "JAMLR_0053_nn_P_count_sub.txt"
Joining with `by = join_by(ENSG)`
[1] "JAMLR_0054_nn_P_count_sub.txt"
Joining with `by = join_by(ENSG)`
[1] "JAMLR_0055_nn_M_count_sub.txt"
Warning: Stopped early on line 57774. Expected 2 fields but found 3. Consider fill=TRUE and comment.char=. First discarded non-empty line: <<__no_feature       733049>>Joining with `by = join_by(ENSG)`
[1] "JAMLR_0055_nn_P_count_sub.txt"
Joining with `by = join_by(ENSG)`
kim_phALL_counts
ENSGconvert <- data.table::fread('../../../../../../../CIBERSORT/newDataSets_Jul2020/preprocessing/GRCh38_transcript_lengths.txt')
ENSGconvert <- ENSGconvert %>% select(ENSG = V1, Gene = V2) %>% unique()
ENSGconvert
kim_phALL_counts <- kim_phALL_counts %>% inner_join(ENSGconvert) %>% select(-ENSG) %>% select(Gene, everything()) %>% 
  group_by(Gene) %>% summarise_all(sum)
Joining with `by = join_by(ENSG)`
kim_phALL_counts
kim_phALL_counts  %>% write_csv('subtype_subcluster/Kim2023_Ph_BALL/Kim2023_Ph_BALL_RNAseq_counts.csv')
library(DESeq2)

kim_phALL_vst <- DESeqDataSetFromMatrix(kim_phALL_counts %>% column_to_rownames('Gene') %>% data.matrix() , 
                       colData = data.frame('Patient' = colnames(kim_phALL_counts)[-1]) %>% column_to_rownames('Patient'), 
                       design = ~1) %>% vst() %>% assay()
rm(kim_phALL_counts)

kim_phALL_vst[1:10,1:10]
          JAMLR_0003_nn_P JAMLR_0004_nn_P JAMLR_0005_nn_P JAMLR_0006_nn_P JAMLR_0007_nn_P JAMLR_0008_nn_P JAMLR_0009_nn_P JAMLR_0010_nn_P JAMLR_0011_nn_P
5S_rRNA          6.403128        6.403128        6.403128        6.403128        6.403128        6.403128        6.403128        6.403128        6.403128
7SK             13.180453       12.333339       11.877076       10.784501       11.035531       10.744278       11.414756       11.357774       10.528394
A1BG             6.541529        6.533009        6.610118        6.664031        6.614425        6.403128        6.528962        6.548396        6.606195
A1BG-AS1         7.733125        8.601058        8.214180        9.511347        7.858343        7.275169        8.648863        8.115911        8.337477
A1CF             6.403128        6.403128        6.403128        6.403128        6.403128        6.403128        6.403128        6.403128        6.403128
A2M             10.222182       10.538301        6.403128        7.063431       11.429050       10.965017        7.799619        8.368576        9.622616
A2M-AS1          7.400566        7.084282        6.886712        6.814816        7.249977        6.976037        7.262451        6.812803        6.403128
A2ML1            6.403128        6.403128        6.403128        6.403128        6.403128        6.403128        6.403128        6.403128        6.403128
A2ML1-AS1        6.403128        6.403128        6.403128        6.403128        6.403128        6.403128        6.403128        6.403128        6.403128
A2ML1-AS2        6.403128        6.403128        6.403128        6.403128        6.403128        6.403128        6.403128        6.403128        6.403128
          JAMLR_0012_nn_P
5S_rRNA          6.403128
7SK             11.256461
A1BG             6.707218
A1BG-AS1         8.305869
A1CF             6.403128
A2M              7.226245
A2M-AS1          6.927905
A2ML1            6.403128
A2ML1-AS1        6.403128
A2ML1-AS2        6.403128
kim_phALL_vst %>% as.data.frame() %>% rownames_to_column('Gene') %>% write_csv('subtype_subcluster/Kim2023_Ph_BALL/Kim2023_Ph_BALL_RNAseq_vst.csv')
# calculate NMF scores from vst-normalized data
kim_phALL_vst_NMFscores <- calculate_NMFscores(kim_phALL_vst, modelweights, scale = T, sampleID = 'Sample')
[1] "Warning: 7 genes from NMF models are missing from query dataset"
kim_phALL_vst_NMFscores[1:10,1:10]
kim_phALL_vst_NMFscores %>% write_csv('subtype_subcluster/Kim2023_Ph_BALL/Kim2023_Ph_BALL_LineageNMF_Scored.csv')

compare

Kim_PhALL_anno %>% mutate(Sample = ifelse(Manuscript_name %>% str_detect('-R'), paste0(JAMLR, '_nn_M'), paste0(JAMLR, '_nn_P'))) %>% 
  write_csv('subtype_subcluster/Kim2023_Ph_BALL/Kim2023_Ph_BALL_anno_cleaned.csv')
Kim_PhALL_anno_LineageScored <- Kim_PhALL_anno %>% mutate(Sample = ifelse(Manuscript_name %>% str_detect('-R'), paste0(JAMLR, '_nn_M'), paste0(JAMLR, '_nn_P'))) %>% 
  select(Sample, WBC, Age_at_dx, subtype, Subgroup) %>% 
  inner_join(kim_phALL_vst_NMFscores)
Joining with `by = join_by(Sample)`
Kim_PhALL_anno_LineageScored
Kim_PhALL_anno %>% pull(Age_at_dx) %>% summary()
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max.    NA's 
  13.00   41.25   51.00   51.23   62.75   88.00       6 
Kim_PhALL_anno_LineageScored %>% 
  select(-Sample, -WBC, -Age_at_dx, -subtype) %>% 
  pivot_longer(-Subgroup) %>% 
  ggplot(aes(x = Subgroup, y = value, fill = Subgroup)) + 
  geom_boxplot() + ggbeeswarm::geom_quasirandom() +
  theme_pubr(legend = 'none') + theme(axis.text.x = element_text(angle = 90, hjust = 1, vjust = 0.5)) +
  facet_wrap(.~name, scales = 'free', ncol = 5) + stat_compare_means()

LS0tCnRpdGxlOiAiTEFTU08gUmVncmVzc2lvbiIKb3V0cHV0OiBodG1sX25vdGVib29rCi0tLQoKTEFTU08gcmVncmVzc2lvbiB1c2luZyB0aHJlc2hvbGRlZCBOTUYgY29yciBnZW5lcz8gCgojIyBSZWdyZXNzIG9uIE5NRiBzY29yZQoKUHNldWRvYnVsayBFeHByZXNzaW9uCgpgYGB7cn0KcHNldWRvYnVsa192c3QgPC0gcmVhZFJEUygiLi4vQkFMTGJ1bGtfRGVjb252b2x1dGlvbi9CQUxMXzg5cHRfcHNldWRvYnVsa192c3QucmRzIikKcHNldWRvYnVsa192c3QKYGBgCgoKYGBge3J9Ck5NRl9wdHNjb3JlcyA8LSByZWFkX2NzdignLi4vQ29tcG9zaXRpb25BbmFseXNpcy9CQUxMX0NvbXBvc2l0aW9uX0RldlN0YXRlX05NRnNjb3Jlcy5jc3YnKSAlPiUgCiAgbGVmdF9qb2luKHJlYWRfY3N2KCdzY0JBTExfSURjb252ZXJzaW9uLmNzdicpKSAlPiUKICBzZWxlY3QoSURfQnVsayA9IElELCBjb250YWlucygnTk1GJykpICU+JQogIHBpdm90X2xvbmdlcigtSURfQnVsaywgbmFtZXNfdG8gPSAnTk1GJywgdmFsdWVzX3RvID0gJ05NRnNjb3JlJykgJT4lIAogIGxlZnRfam9pbihwc2V1ZG9idWxrX3ZzdEBtZXRhLmRhdGEgJT4lIHNlbGVjdChJRF9CdWxrKSAlPiUgcm93bmFtZXNfdG9fY29sdW1uKCdJRF9zY1JOQScpICkgJT4lIAogIG11dGF0ZShMaW5lYWdlID0gTk1GICU+JSBzdHJfcmVwbGFjZSgnLipfJywnJyksIE5NRiA9IE5NRiAlPiUgc3RyX3JlcGxhY2UoJ18uKicsJycpKSAlPiUgCiAgc2VsZWN0KFBhdGllbnQgPSBJRF9zY1JOQSwgTk1GLCBMaW5lYWdlLCBOTUZzY29yZSkgCgpOTUZfcHRzY29yZXMKYGBgCgpgYGB7cn0KTk1GY29yciA8LSByZWFkX2NzdignTk1GX0dlbmVDb3JyX1RocmVzaG9sZGluZy5jc3YnKSAlPiUgCiAgZmlsdGVyKHRocmVzaG9sZCA9PSAncGFzcycsIHF2YWx1ZSA8IDAuMDEpICU+JSBhcnJhbmdlKHF2YWx1ZSkgJT4lIGFycmFuZ2UoTk1GKQpOTUZjb3JyCmBgYAoKIyMjIE5lc3RlZCBDcm9zcyBWYWxpZGF0aW9uIGJ5IEZlYXR1cmUgc2V0CgpEZWZpbmUgZmVhdHVyZSBzcGFjZSAtIGxvYWQgbWFya2VycyAKCgpgYGB7cn0KTGluREVfRkRSMDFfZ2VuZXMgPC0gcmVhZF9jc3YoJ0JBTExfREVyZXN1bHRzX05NRl9MaW5lYWdlLmNzdicpICU+JSBmaWx0ZXIocGFkaiA8IDAuMDEsIHN0YXQgPiAwKSAlPiUgcHVsbChHZW5lKSAlPiUgdW5pcXVlKCkKTGluREVfRkRSMDVfZ2VuZXMgPC0gcmVhZF9jc3YoJ0JBTExfREVyZXN1bHRzX05NRl9MaW5lYWdlLmNzdicpICU+JSBmaWx0ZXIocGFkaiA8IDAuMDUsIHN0YXQgPiAwKSAlPiUgcHVsbChHZW5lKSAlPiUgdW5pcXVlKCkKQkRldkRFX0ZEUjAxX2dlbmVzIDwtIHJlYWRfY3N2KCcuLi9CRGV2ZWxvcG1lbnRfQ2hhcmFjdGVyaXphdGlvbi9CRGV2ZWxvcG1lbnRfQ2VsbFR5cGVfREVyZXN1bHRzLmNzdicpICU+JSAKICBmaWx0ZXIocGFkaiA8IDAuMDEsIHN0YXQgPiAwKSAlPiUgcHVsbChHZW5lKSAlPiUgdW5pcXVlKCkKQkRldkRFX0ZEUjA1X2dlbmVzIDwtIHJlYWRfY3N2KCcuLi9CRGV2ZWxvcG1lbnRfQ2hhcmFjdGVyaXphdGlvbi9CRGV2ZWxvcG1lbnRfQ2VsbFR5cGVfREVyZXN1bHRzLmNzdicpICU+JSAKICBmaWx0ZXIocGFkaiA8IDAuMDUsIHN0YXQgPiAwKSAlPiUgcHVsbChHZW5lKSAlPiUgdW5pcXVlKCkKYGBgCgpgYGB7cn0KTk1GY29yciA8LSBOTUZjb3JyICU+JSBtdXRhdGUoTGluREVfRkRSMDUgPSBHZW5lICVpbiUgTGluREVfRkRSMDVfZ2VuZXMsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBMaW5ERV9GRFIwMSA9IEdlbmUgJWluJSBMaW5ERV9GRFIwMV9nZW5lcywgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIEJEZXZERV9GRFIwMSA9IEdlbmUgJWluJSBCRGV2REVfRkRSMDFfZ2VuZXMsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBCRGV2REVfRkRSMDUgPSBHZW5lICVpbiUgQkRldkRFX0ZEUjA1X2dlbmVzKQpOTUZjb3JyCmBgYAoKYGBge3J9CmV2YWx1YXRlX21vZGVsIDwtIGZ1bmN0aW9uKG1vZGVsLCB4X3ZhbCwgYW5ub192YWwsIGxhbWJkYSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgIGZlYXR1cmVfbmFtZSwgaXRlcmF0aW9uLCBmb2xkbmFtZSl7CgogICMgQ3JlYXRlIHNjb3JlIGNsYXNzaWZpY2F0aW9uIHdpdGggc3Vydml2YWwgYW5kIGdldCBjb3ZhcmlhdGVzCiAgcHJlZF95IDwtIHByZWRpY3QobW9kZWwsIHhfdmFsLCBzID0gbGFtYmRhKSAlPiUgZGF0YS5mcmFtZSgpCiAgY29sbmFtZXMocHJlZF95KSA8LSAnUHJlZFNjb3JlJwogIHByZWRfeSA8LSBwcmVkX3kgJT4lIHJvd25hbWVzX3RvX2NvbHVtbignUGF0aWVudCcpICU+JSAKICAgICMgYWRkIGFubm8gdG8gZ2V0IGNvdmFyaWF0ZXMKICAgIGxlZnRfam9pbihhbm5vX3ZhbCwgYnkgPSAnUGF0aWVudCcpCiAgCiAgIyBDYWxjdWxhdGUgY29ycmVsYXRpb24gaW4gdmFsaWRhdGlvbiBzZXQKICBwZWFyc29uIDwtIGNvcihwcmVkX3kkUHJlZFNjb3JlLCBwcmVkX3kkTk1Gc2NvcmUsIG1ldGhvZCA9ICdwZWFyc29uJykKICBzcGVhcm1hbiA8LSBjb3IocHJlZF95JFByZWRTY29yZSwgcHJlZF95JE5NRnNjb3JlLCBtZXRob2QgPSAnc3BlYXJtYW4nKQogIAogICMgU3VtbWFyeSBNZXRyaWNzCiAgc3VtbWFyeV9tZXRyaWNzIDwtIGRhdGEuZnJhbWUoCiAgICAnbW9kZWxfaWQnID0gcGFzdGUwKGZlYXR1cmVfbmFtZSwgJ19pdGVyJywgaXRlcmF0aW9uLCAnXycsIGZvbGRuYW1lKSwKICAgICdsYW1iZGEnID0gbGFtYmRhLAogICAgJ21vZGVsX3NpemUnID0gc3VtKGNvZWYobW9kZWwsIHMgPSBsYW1iZGEpIT0wKSwKICAgICdwZWFyc29uJyA9IHBlYXJzb24sCiAgICAnc3BlYXJtYW4nID0gc3BlYXJtYW4sCiAgICAnZmVhdHVyZXMnID0gZmVhdHVyZV9uYW1lLAogICAgJ2l0ZXJhdGlvbicgPSBpdGVyYXRpb24sCiAgICAnZm9sZG5hbWUnID0gZm9sZG5hbWUKICApCiAgcmV0dXJuKHN1bW1hcnlfbWV0cmljcykKfQoKYGBgCgoKYGBge3J9CmdyaWRzZWFyY2hfbGFzc28gPC0gZnVuY3Rpb24oZXhwcl90cmFpbiwgZXhwcl92YWwsIGFubm9fdHJhaW4sIGFubm9fdmFsLCBmZWF0dXJlcywgZmVhdHVyZV9uYW1lLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIGl0ZXJhdGlvbiwgZm9sZG5hbWUsIHN1bW1hcnlfbWV0cmljcyl7CiAgCiAgIyBGaWx0ZXIgZXhwciBtYXRyaXggZm9yIGZlYXR1cmUgc2V0CiAgeF90cmFpbiA8LSBleHByX3RyYWluWywgY29sbmFtZXMoZXhwcl90cmFpbikgJWluJSBmZWF0dXJlc10KICB4X3ZhbCA8LSBleHByX3ZhbFssIGNvbG5hbWVzKGV4cHJfdmFsKSAlaW4lIGZlYXR1cmVzXQoKICAjIFRyYWluIExBU1NPIAogIG1vZGVsIDwtIHRyYWluX0xBU1NPKHhfdHJhaW4sIGFubm9fdHJhaW4pCgogICMgR2V0IHN1bW1hcnkgbWV0cmljcyBmb3IgbGFtYmRhLm1pbiBhbmQgbGFtYmRhLjFzZQogIGZvcihsYW1iZGEgaW4gYygnbGFtYmRhLm1pbicsICdsYW1iZGEuMXNlJykpewogICAgc3VtbWFyeV9tZXRyaWNzIDwtIHN1bW1hcnlfbWV0cmljcyAlPiUgcmJpbmQoCiAgICAgIGV2YWx1YXRlX21vZGVsKG1vZGVsID0gbW9kZWwsIHhfdmFsID0geF92YWwsIGFubm9fdmFsID0gYW5ub192YWwsIGxhbWJkYSA9IGxhbWJkYSwgCiAgICAgICAgICAgICAgICAgICAgIGZlYXR1cmVfbmFtZSA9IGZlYXR1cmVfbmFtZSwgaXRlcmF0aW9uID0gaXRlcmF0aW9uLCBmb2xkbmFtZSA9IGZvbGRuYW1lKSkKICB9CiAgCiAgcmV0dXJuKHN1bW1hcnlfbWV0cmljcykKfQoKYGBgCgoKYGBge3J9Cm5lc3RlZENWX3JlZ3Jlc3Npb24gPC0gZnVuY3Rpb24odHJhaW5fYW5ubywgdHJhaW5fZXhwciwgaXRlcmF0aW9uLCBmZWF0dXJlX3NldHMsIHN1bW1hcnlfbWV0cmljcyl7CiAgIyBzZXQgdXAgcmFuZG9tIHNlZWQgYW5kIHNodWZmbGUgZGF0YSAKICBzZXQuc2VlZChpdGVyYXRpb24pCiAgdHJhaW5fYW5ubyA8LSB0cmFpbl9hbm5vW3NhbXBsZShucm93KHRyYWluX2Fubm8pKSxdCiAgdHJhaW5fZXhwciA8LSB0cmFpbl9leHByW3NhbXBsZShucm93KHRyYWluX2V4cHIpKSxdCiAgCiAgIyMgNS1mb2xkIG91dGVyIGNyb3NzIHZhbGlkYXRpb24KICBmb2xkcyA8LSByc2FtcGxlOjp2Zm9sZF9jdih0cmFpbl9hbm5vLCA1KQogIGZvcihvdXRlcl9jdiBpbiAxOjUpewogICAgIyBmb2xkIElECiAgICBmb2xkbmFtZSA8LSBmb2xkcyRpZFtbb3V0ZXJfY3ZdXQogICAgIyBnZXQgYW5ubyBzcGxpdHMKICAgIGFubm9fdHJhaW4gPC0gYW5hbHlzaXMoZm9sZHMkc3BsaXRzW1tvdXRlcl9jdl1dKQogICAgYW5ub192YWwgPC0gYXNzZXNzbWVudChmb2xkcyRzcGxpdHNbW291dGVyX2N2XV0pCiAgICAjIGdldCBleHByIHNwbGl0cwogICAgZXhwcl90cmFpbiA8LSB0cmFpbl9leHByW2Fubm9fdHJhaW4kUGF0aWVudCxdCiAgICBleHByX3ZhbCA8LSB0cmFpbl9leHByW2Fubm9fdmFsJFBhdGllbnQsXQogICAgCiAgICAjIEl0ZXJhdGUgdGhyb3VnaCBmZWF0dXJlIHNldCBhbmQgcnVuIGdyaWRzZWFyY2ggdG8gdHJhaW4gc3Vydml2YWwgZnVuY3Rpb25zCiAgICBmb3IoZmVhdHVyZV9uYW1lIGluIG5hbWVzKGZlYXR1cmVfc2V0cykpewogICAgICAjIGdldCBmZWF0dXJlIGxpc3QKICAgICAgZmVhdHVyZXMgPC0gZmVhdHVyZV9zZXRzW1tmZWF0dXJlX25hbWVdXQogICAgICAjIHJ1biBncmlkc2VhcmNoIGFuZCBnZXQgcmVzdWx0cwogICAgICBzdW1tYXJ5X21ldHJpY3MgPC0gZ3JpZHNlYXJjaF9sYXNzbyhleHByX3RyYWluID0gZXhwcl90cmFpbiwgZXhwcl92YWwgPSBleHByX3ZhbCwgYW5ub190cmFpbiA9IGFubm9fdHJhaW4sIGFubm9fdmFsID0gYW5ub192YWwsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZmVhdHVyZXMgPSBmZWF0dXJlcywgZmVhdHVyZV9uYW1lID0gZmVhdHVyZV9uYW1lLCBpdGVyYXRpb24gPSBpdGVyYXRpb24sIGZvbGRuYW1lID0gZm9sZG5hbWUsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzdW1tYXJ5X21ldHJpY3MgPSBzdW1tYXJ5X21ldHJpY3MpCiAgICB9CiAgfQogIHJldHVybihzdW1tYXJ5X21ldHJpY3MpCn0KCmBgYAoKCmBgYHtyfQpsaWJyYXJ5KHRpZHltb2RlbHMpCmxpYnJhcnkoZ2xtbmV0KQoKb3V0cHV0IDwtIGRhdGEuZnJhbWUoKQp0cmFpbl94IDwtIHBzZXVkb2J1bGtfdnN0QGFzc2F5cyRSTkFAZGF0YVssdW5pcXVlKE5NRl9wdHNjb3JlcyRQYXRpZW50KV0gJT4lIGRhdGEubWF0cml4KCkgJT4lIHQoKQoKZm9yKE5NRmNvbXAgaW4gYygnTk1GMScsICdOTUYyJywgJ05NRjMnLCAnTk1GNCcsICdOTUY1JywgJ05NRjYnLCAnTk1GNycsICdOTUY4JywgJ05NRjknLCAnTk1GMTAnKSl7CiAgCiAgcHJpbnQocGFzdGUwKCdOTUYgQ29tcG9uZW50OiAnLCBOTUZjb21wKSkKICAKICB0ZW1wX291dHB1dCA8LSBkYXRhLmZyYW1lKCkKICAKICB0cmFpbl95IDwtIE5NRl9wdHNjb3JlcyAlPiUgZmlsdGVyKE5NRiA9PSBOTUZjb21wKSAlPiUgc2VsZWN0KFBhdGllbnQsIE5NRnNjb3JlKQogIGZlYXR1cmVzcGFjZSA8LSBsaXN0KCdQb3NDb3JyX0xpbkRFX0ZEUjA1JyA9IE5NRmNvcnIgJT4lIGZpbHRlcihOTUYgPT0gTk1GY29tcCwgTGluREVfRkRSMDUgPT0gVFJVRSwgcGVhcnNvbiA+IDApICU+JSBwdWxsKEdlbmUpLCAKICAgICAgICAgICAgICAgICAgICAgICAnUG9zQ29ycl9MaW5ERV9GRFIwMScgPSBOTUZjb3JyICU+JSBmaWx0ZXIoTk1GID09IE5NRmNvbXAsIExpbkRFX0ZEUjAxID09IFRSVUUsIHBlYXJzb24gPiAwKSAlPiUgcHVsbChHZW5lKSwgCiAgICAgICAgICAgICAgICAgICAgICAgJ1Bvc0NvcnJfTGluREVfQkRldkRFX0ZEUjA1JyA9IE5NRmNvcnIgJT4lIGZpbHRlcihOTUYgPT0gTk1GY29tcCwgTGluREVfRkRSMDUgPT0gVFJVRSwgQkRldkRFX0ZEUjA1ID09IFRSVUUsIHBlYXJzb24gPiAwKSAlPiUgcHVsbChHZW5lKSwgCiAgICAgICAgICAgICAgICAgICAgICAgJ1Bvc0NvcnJfTGluREVfQkRldkRFX0ZEUjAxJyA9IE5NRmNvcnIgJT4lIGZpbHRlcihOTUYgPT0gTk1GY29tcCwgTGluREVfRkRSMDEgPT0gVFJVRSwgQkRldkRFX0ZEUjAxID09IFRSVUUsIHBlYXJzb24gPiAwKSAlPiUgcHVsbChHZW5lKSwgCiAgICAgICAgICAgICAgICAgICAgICAgJ0FueUNvcnJfTGluREVfRkRSMDUnID0gTk1GY29yciAlPiUgZmlsdGVyKE5NRiA9PSBOTUZjb21wLCBMaW5ERV9GRFIwNSA9PSBUUlVFKSAlPiUgcHVsbChHZW5lKSwKICAgICAgICAgICAgICAgICAgICAgICAnQW55Q29ycl9MaW5ERV9GRFIwMScgPSBOTUZjb3JyICU+JSBmaWx0ZXIoTk1GID09IE5NRmNvbXAsIExpbkRFX0ZEUjAxID09IFRSVUUpICU+JSBwdWxsKEdlbmUpLAogICAgICAgICAgICAgICAgICAgICAgICdBbnlDb3JyX0xpbkRFX0JEZXZERV9GRFIwNScgPSBOTUZjb3JyICU+JSBmaWx0ZXIoTk1GID09IE5NRmNvbXAsIExpbkRFX0ZEUjA1ID09IFRSVUUsIEJEZXZERV9GRFIwNSA9PSBUUlVFKSAlPiUgcHVsbChHZW5lKSwgCiAgICAgICAgICAgICAgICAgICAgICAgJ0FueUNvcnJfTGluREVfQkRldkRFX0ZEUjAxJyA9IE5NRmNvcnIgJT4lIGZpbHRlcihOTUYgPT0gTk1GY29tcCwgTGluREVfRkRSMDEgPT0gVFJVRSwgQkRldkRFX0ZEUjAxID09IFRSVUUpICU+JSBwdWxsKEdlbmUpCiAgKQogIAogIGZvcihpdGVyYXRpb24gaW4gMToxMCl7CiAgICBwcmludChwYXN0ZTAoJ2l0ZXJhdGlvbiAnLCBpdGVyYXRpb24pKQogICAgdGVtcF9vdXRwdXQgPC0gbmVzdGVkQ1ZfcmVncmVzc2lvbih0cmFpbl9hbm5vID0gdHJhaW5feSwgdHJhaW5fZXhwciA9IHRyYWluX3gsIGl0ZXJhdGlvbiA9IGl0ZXJhdGlvbiwgZmVhdHVyZV9zZXRzID0gZmVhdHVyZXNwYWNlLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc3VtbWFyeV9tZXRyaWNzID0gdGVtcF9vdXRwdXQpIAogIH0KICAjIyBhbm5vdGF0ZSBhbmQgYWRkIHRvIGZpbmFsIG91dHB1dAogIG91dHB1dCA8LSBiaW5kX3Jvd3Mob3V0cHV0LCB0ZW1wX291dHB1dCAlPiUgbXV0YXRlKE5NRiA9IE5NRmNvbXApKQp9CgpvdXRwdXQgJT4lIHdyaXRlX2NzdignUmVwTmVzdGVkQ1ZfcmVzdWx0c19OTUZyZWdyZXNzaW9uLmNzdicpCmBgYAoKCioqNS1Gb2xkIGNyb3NzIHZhbGlkYXRpb24gd2l0aCAxMCByZXBlYXRzIHdpdGhpbiB0aGUgcHNldWRvYnVsayB0byBlc3RpbWF0ZSB0aGUgYmVzdCBwYXJhbWV0ZXJzIGFuZCBnZXQgYSBnZXN0YWx0IG9mIHRoZSBvdmVyYWxsIGFjY3VyYWN5KioKQWZ0ZXIgY2hvb3NpbmcgdGhlIGJlc3QgY29tYmluYXRpb24gb2YgcGFyYW1ldGVycyB3ZSB3aWxsIHRlc3Qgb24gdGhlIGJ1bGsgUk5BLXNlcSBkYXRhc2V0LgoKCmBgYHtyfQpvdXRwdXQgPC0gcmVhZF9jc3YoJ1JlcE5lc3RlZENWX3Jlc3VsdHNfTk1GcmVncmVzc2lvbi5jc3YnKSAKb3V0cHV0ICU+JSBwdWxsKGZlYXR1cmVzKSAlPiUgdGFibGUoKQpgYGAKCmBgYHtyLCBmaWcuaGVpZ2h0ID0gNiwgZmlnLndpZHRoID0gMTJ9Cm91dHB1dCAlPiUgCiAgbXV0YXRlKE5NRiA9IGZhY3RvcihOTUYsIGxldmVscyA9IGMoJ05NRjEnLCAnTk1GMicsICdOTUYzJywgJ05NRjQnLCAnTk1GNScsICdOTUY2JywgJ05NRjcnLCAnTk1GOCcsICdOTUY5JywgJ05NRjEwJykpKSAlPiUgCiAgZ2dwbG90KGFlcyh4ID0gcmVvcmRlcihmZWF0dXJlcywgLW1vZGVsX3NpemUpLCB5ID0gbW9kZWxfc2l6ZSwgZmlsbCA9IGxhbWJkYSkpICsgCiAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gMTAsIGx0eSA9IDIpICsgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gMjAsIGx0eSA9IDIpICsgCiAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gMzAsIGx0eSA9IDIpICsgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gNDAsIGx0eSA9IDIpICsgCiAgZ2VvbV9ib3hwbG90KG91dGxpZXIuc2l6ZSA9IDAuOCkgKyBnZ2JlZXN3YXJtOjpnZW9tX3F1YXNpcmFuZG9tKGRvZGdlLndpZHRoID0gMC43LCBzaXplID0gMC4yLCBhbHBoYSA9IDAuNykgKyAKICBmYWNldF93cmFwKC5+Tk1GLCBuY29sID0gNikgKyB0aGVtZV9wdWJyKCkgKyAKICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDkwLCBoanVzdCA9IDEsIHZqdXN0ID0gMC41KSkgKyAKICBzdGF0X2NvbXBhcmVfbWVhbnMobGFiZWwgPSAncC5zaWduaWYnKQogIApgYGAKCgpgYGB7ciwgZmlnLmhlaWdodCA9IDYsIGZpZy53aWR0aCA9IDEyfQpvdXRwdXQgJT4lIAogIG11dGF0ZShOTUYgPSBmYWN0b3IoTk1GLCBsZXZlbHMgPSBjKCdOTUYxJywgJ05NRjInLCAnTk1GMycsICdOTUY0JywgJ05NRjUnLCAnTk1GNicsICdOTUY3JywgJ05NRjgnLCAnTk1GOScsICdOTUYxMCcpKSkgJT4lIAogIGdncGxvdChhZXMoeCA9IHJlb3JkZXIoZmVhdHVyZXMsIC1wZWFyc29uKSwgeSA9IHBlYXJzb24sIGZpbGwgPSBsYW1iZGEpKSArIAogIGdlb21fYm94cGxvdChvdXRsaWVyLnNpemUgPSAwLjgpICsgZ2diZWVzd2FybTo6Z2VvbV9xdWFzaXJhbmRvbShkb2RnZS53aWR0aCA9IDAuNywgc2l6ZSA9IDAuMiwgYWxwaGEgPSAwLjcpICsgCiAgZmFjZXRfd3JhcCgufk5NRiwgbmNvbCA9IDYpICsgdGhlbWVfcHVicigpICsgCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA5MCwgaGp1c3QgPSAxLCB2anVzdCA9IDAuNSkpICsgCiAgc3RhdF9jb21wYXJlX21lYW5zKGxhYmVsID0gJ3Auc2lnbmlmJykKICAKYGBgCgoKYGBge3J9Ck5NRmNvbnZlcnQKYGBgCgoKSW4gc3VtbWFyeSwgdXNpbmcgY29tYmluYXRpb24gb2YgcG9zaXRpdmVseSBhbmQgbmVnYXRpdmVseSBjb3JyZWxhdGVkIGdlbmVzIGxlYWRzIHRvIGJldHRlciBwZXJmb3JtYW5jZSwgcGFydGljdWxhcmx5IGZvcjogCiAgTk1GMSAoUHJvLUIpCiAgTk1GNSAoRXJ5dGhyb2lkKQogIE5NRjExIChOYWl2ZSBUKQogIApJbiB0ZXJtcyBvZiBmaWx0ZXJpbmcgb2YgY29ycmVsYXRlZCBnZW5lczsgTGluIERFIEJEZXYgREUgRkRSIDwgMC4wMSBhcyBhIGZpbHRlciBjb25zaXN0ZW50bHkgbGVhZCB0byB0aGUgYmVzdCBwZXJmb3JtYW5jZS4gCgpMYW1iZGEgY2hvaWNlIG9mIE1pbmltdW0gKyAxU0UgcmVzdWx0ZWQgaW4gbW9kZWwgc2l6ZSByZWR1Y3Rpb25zIG9mIG5lYXJseSAyMCBnZW5lcyB3aXRob3V0IHNhY3JpZmljaW5nIHBlcmZvcm1hbmNlIGJ5IG5lc3RlZCBDVi4gCgpgYGB7cn0Kb3V0cHV0X0NWbWVkaWFucyA8LSBvdXRwdXQgJT4lIGZpbHRlcihsYW1iZGEgPT0gJ2xhbWJkYS4xc2UnLCBmZWF0dXJlcyA9PSAnQW55Q29ycl9MaW5ERV9CRGV2REVfRkRSMDEnKSAlPiUgI0FueUNvcnJfTGluREVfRkRSMDEuIEFueUNvcnJfTGluREVfQkRldkRFX0ZEUjAxCiAgc2VsZWN0KE5NRiwgbW9kZWxfc2l6ZSwgcGVhcnNvbiwgc3BlYXJtYW4pICU+JSAKICBncm91cF9ieShOTUYpICU+JSBzdW1tYXJpc2VfYWxsKG1lZGlhbikgJT4lIGFycmFuZ2UocGVhcnNvbikKCm91dHB1dF9DVm1lZGlhbnMgCmBgYAoKCgojIyMgRmluYWwgQ2hvaWNlOiBDb3JyIFRocmVzaG9sZCArIExpbmVhZ2UgREUgRkRSMDE7IGxhbWJkYSBtaW4gKyAxU0UKClRyYWluIGZyb20gYm90aCBwb3NpdGl2ZWx5IGNvcnIgYW5kIGFueSBjb3JyLiBJZiBtb2RlbHMgZnJvbSBib3RoIGFwcHJvYWNoZXMgaGF2ZSBuZWdhdGl2ZSBjb2VmZmljaWVudHMsIHVzZSBhbnkgY29yci4gCkFsc28gbWFrZSBzdXJlIHRoYXQgdGhlIGdlbmVzIGFyZSBwcmVzZW50IGluIHRoZSBidWxrIFJOQXNlcSBkYXRhCgoKYGBge3J9CnRyYWluX0xBU1NPIDwtIGZ1bmN0aW9uKHhfdHJhaW4sIHlfdHJhaW4sIGFscGhhID0gMSl7CiAgCiAgdHJhaW5feSA8LSB5X3RyYWluJE5NRnNjb3JlCiAgCiAgIyBQZXJmb3JtIExhc3NvIHJlZ3Jlc3Npb24gd2l0aCBMT09DViAKICBtb2RlbCA8LSBjdi5nbG1uZXQoeCA9IHhfdHJhaW4sIHkgPSB0cmFpbl95LCBuZm9sZCA9IGRpbSh4X3RyYWluKVsxXSwgZmFtaWx5ID0gJ2dhdXNzaWFuJywgYWxwaGEgPSBhbHBoYSwgbWF4aXQ9MTAwMDAwMCwgc3RhbmRhcmRpemU9RkFMU0UpCiAgI3Bsb3QobW9kZWwpCgogIHJldHVybihtb2RlbCkKfQpgYGAKCmBgYHtyfQpidWxrUk5BZ2VuZXMgPC0gZGF0YS50YWJsZTo6ZnJlYWQoIi4uL0JBTExidWxrX0RlY29udm9sdXRpb24vQkFMTF9idWxrUk5BX2RhdGEvQkFMTF9CdWxrUk5Bc2VxX3N1YnNldGdlbmVfcmF3Y291bnRzLnR4dCIpJEdlbmUKYnVsa1JOQWdlbmVzICU+JSBsZW5ndGgoKQpgYGAKCgpgYGB7cn0Kc2V0LnNlZWQoMTIzKQptb2RlbF9saXN0IDwtIGxpc3QoKQojIHN1YnNldCBOTUYgY29yciB3aXRoIGdlbmVzIHByZXNlbnQgaW4gdGhlIGJ1bGsgUk5BIGRhdGEKTk1GY29yciA8LSBOTUZjb3JyICU+JSBmaWx0ZXIoR2VuZSAlaW4lIGJ1bGtSTkFnZW5lcykKCmZvcihOTUZjb21wIGluIGMoJ05NRjEnLCAnTk1GMicsICdOTUYzJywgJ05NRjQnLCAnTk1GNScsICdOTUY2JywgJ05NRjcnLCAnTk1GOCcsICdOTUY5JywgJ05NRjEwJykpewogIAogICMgRGVmaW5lIHkgdmFyaWFibGU7IE5NRiBzY29yZQogIHRyYWluX3kgPC0gTk1GX3B0c2NvcmVzICU+JSBmaWx0ZXIoTk1GID09IE5NRmNvbXApICU+JSBzZWxlY3QoUGF0aWVudCwgTk1Gc2NvcmUpCiAgIyBEZWZpbmUgZmVhdHVyZSBzcGFjZSB0byB0cmFpbiBmcm9tCiAgZmVhdHVyZV9zcGFjZSA8LSBOTUZjb3JyICU+JSBmaWx0ZXIoTk1GID09IE5NRmNvbXAsIExpbkRFX0ZEUjAxID09IFRSVUUsIEJEZXZERV9GRFIwMSA9PSBUUlVFKSAlPiUgcHVsbChHZW5lKSAjQkRldkRFX0ZEUjAxID09IFRSVUUKICAjIHN1YnNldCB0cmFpbmluZyBzZXQKICB0cmFpbl94IDwtIHBzZXVkb2J1bGtfdnN0QGFzc2F5cyRSTkFAZGF0YVtmZWF0dXJlX3NwYWNlLCB0cmFpbl95JFBhdGllbnRdICU+JSBkYXRhLm1hdHJpeCgpICU+JSB0KCkKICAKICBtb2RlbCA8LSB0cmFpbl9MQVNTTyh0cmFpbl94LCB5X3RyYWluID0gdHJhaW5feSkKICBtb2RlbF9saXN0W1tOTUZjb21wXV0gPC0gbW9kZWwKfQpgYGAKCmBgYHtyfQojIG1vZGVsIHdlaWdodHMgCm1vZGVsd2VpZ2h0cyA8LSBkYXRhLmZyYW1lKCkKCmZvcihOTUZjb21wIGluIGMoJ05NRjEnLCAnTk1GMicsICdOTUYzJywgJ05NRjQnLCAnTk1GNScsICdOTUY2JywgJ05NRjcnLCAnTk1GOCcsICdOTUY5JywgJ05NRjEwJykpewogIG1vZGVsd2VpZ2h0cyA8LSBtb2RlbHdlaWdodHMgJT4lIGJpbmRfcm93cygKICAgIG1vZGVsX2xpc3RbW05NRmNvbXBdXSAlPiUgY29lZihzID0gJ2xhbWJkYS4xc2UnKSAlPiUgZGF0YS5tYXRyaXgoKSAlPiUgCiAgICAgIGRhdGEuZnJhbWUoKSAlPiUgZHBseXI6OnJlbmFtZShXZWlnaHQgPSBzMSkgJT4lIHJvd25hbWVzX3RvX2NvbHVtbignR2VuZScpICU+JSAKICAgICAgdGFpbCgtMSkgJT4lIGZpbHRlcihXZWlnaHQgIT0gMCkgJT4lIGFycmFuZ2UoLVdlaWdodCkgJT4lIG11dGF0ZShNb2RlbCA9IE5NRmNvbXApCiAgKQp9Cgptb2RlbHdlaWdodHMgPC0gbW9kZWx3ZWlnaHRzICU+JSBzZWxlY3QoTW9kZWwsIEdlbmUsIFdlaWdodCkKbW9kZWx3ZWlnaHRzICU+JSBncm91cF9ieShNb2RlbCkgJT4lIHN1bW1hcmlzZShjb3VudCA9IG4oKSkKYGBgCgoKYGBge3J9CiMgbW9kZWwgd2VpZ2h0cyAKbW9kZWx3ZWlnaHRzIDwtIGRhdGEuZnJhbWUoKQoKZm9yKE5NRmNvbXAgaW4gYygnTk1GMScsICdOTUYyJywgJ05NRjMnLCAnTk1GNCcsICdOTUY1JywgJ05NRjYnLCAnTk1GNycsICdOTUY4JywgJ05NRjknLCAnTk1GMTAnKSl7CiAgbW9kZWx3ZWlnaHRzIDwtIG1vZGVsd2VpZ2h0cyAlPiUgYmluZF9yb3dzKAogICAgbW9kZWxfbGlzdFtbTk1GY29tcF1dICU+JSBjb2VmKHMgPSAnbGFtYmRhLjFzZScpICU+JSBkYXRhLm1hdHJpeCgpICU+JSAKICAgICAgZGF0YS5mcmFtZSgpICU+JSBkcGx5cjo6cmVuYW1lKFdlaWdodCA9IHMxKSAlPiUgcm93bmFtZXNfdG9fY29sdW1uKCdHZW5lJykgJT4lIAogICAgICB0YWlsKC0xKSAlPiUgZmlsdGVyKFdlaWdodCAhPSAwKSAlPiUgYXJyYW5nZSgtV2VpZ2h0KSAlPiUgbXV0YXRlKE1vZGVsID0gTk1GY29tcCkKICApCn0KCm1vZGVsd2VpZ2h0cyA8LSBtb2RlbHdlaWdodHMgJT4lIHNlbGVjdChNb2RlbCwgR2VuZSwgV2VpZ2h0KQptb2RlbHdlaWdodHMgJT4lIGdyb3VwX2J5KE1vZGVsKSAlPiUgc3VtbWFyaXNlKGNvdW50ID0gbigpKQpgYGAKCgpgYGB7cn0KbW9kZWx3ZWlnaHRzICU+JSAKICBsZWZ0X2pvaW4oTk1GY29udmVydCAlPiUgZHBseXI6OnJlbmFtZShNb2RlbCA9IE5NRikpICU+JQogIG11dGF0ZShjb2VmZmljaWVudCA9IGlmZWxzZShXZWlnaHQgPiAwLCAnUG9zaXRpdmUnLCAnTmVnYXRpdmUnKSAlPiUgZmFjdG9yKGxldmVscyA9IGMoJ1Bvc2l0aXZlJywgJ05lZ2F0aXZlJykpKSAlPiUgCiAgZ3JvdXBfYnkoTk1GbmFtZWQsIGNvZWZmaWNpZW50KSAlPiUgc3VtbWFyaXNlKGNvdW50ID0gbigpKSAlPiUKICBnZ3Bsb3QoYWVzKHggPSBOTUZuYW1lZCwgeSA9IGNvdW50LCBmaWxsID0gY29lZmZpY2llbnQpKSArIGdlb21fY29sKCkgKyBnZ3B1YnI6OnRoZW1lX3B1YnIoKSArIAogIGdnc2NpOjpzY2FsZV9maWxsX3NpbXBzb25zKCkgKyB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDkwLCBoanVzdCA9IDEpKQpgYGAKCgoKCgpgYGB7cn0KTk1GbmFtZWRfbGV2ZWxzIDwtIGMoJ0hTQ19NUFAnLCAnTXllbG9pZF9Qcm9nJywgJ1ByZV9wREMnLCAnRWFybHlfTHltcGhvaWQnLCAnUHJvX0InLCAnUHJlX0InLCAKICAgICAgICAgICAgICAgICAgICAgICdNYXR1cmVfQicsICdFcnl0aHJvaWQnLCAnTW9ub2N5dGUnLCAnVF9OSycpCgpOTUZjb252ZXJ0IDwtIGRhdGEuZnJhbWUoCiAgJ05NRicgPSBjKCdOTUY4JywgJ05NRjYnLCAnTk1GNycsICdOTUYyJywgJ05NRjEnLCAnTk1GNCcsIAogICAgICAgICAgICAnTk1GNScsICdOTUYzJywgJ05NRjknLCAnTk1GMTAnKSAlPiUgZmFjdG9yKCksCiAgJ05NRm5hbWVkJyA9IE5NRm5hbWVkX2xldmVscyAlPiUgZmFjdG9yKGxldmVscyA9IE5NRm5hbWVkX2xldmVscykKKQoKTk1GY29udmVydApgYGAKCmBgYHtyfQptb2RlbHdlaWdodHMgPC0gbW9kZWx3ZWlnaHRzICU+JSBsZWZ0X2pvaW4oTk1GY29udmVydCAlPiUgZHBseXI6OnJlbmFtZShNb2RlbCA9IE5NRikpICU+JSAKICBhcnJhbmdlKEdlbmUpICU+JSBhcnJhbmdlKE5NRm5hbWVkKSAlPiUgcGl2b3Rfd2lkZXIoaWRfY29scz1HZW5lLCBuYW1lc19mcm9tPU5NRm5hbWVkLCB2YWx1ZXNfZnJvbT1XZWlnaHQpICU+JSByZXBsYWNlKGlzLm5hKC4pLCAwKSAKCm1vZGVsd2VpZ2h0cyAlPiUgd3JpdGVfY3N2KCJOTUZfTGFzc29fTW9kZWxXZWlnaHRzLmNzdiIpCm1vZGVsd2VpZ2h0cwpgYGAKCiMjIEZpZ3VyZSBvdXQgTk1GIHNjb3JpbmcgYW5kIHZhbGlkYXRlIG9uIHBzZXVkb2J1bGsKCmBgYHtyfQpjYWxjdWxhdGVfTk1Gc2NvcmVzID0gZnVuY3Rpb24ocXVlcnksIG1vZGVsd2VpZ2h0cywgc2NhbGUgPSBUUlVFLCBzYW1wbGVJRCA9ICdQYXRpZW50Jyl7CiAgCiAgIyBDaGVjayBmb3Igb3ZlcmxhcCB3aXRoIG1vZGVsIGdlbmVzIGFuZCBxdWVyeSBnZW5lcwogIHF1ZXJ5Z2VuZXMgPC0gcm93bmFtZXMocXVlcnkpCiAgbW9kZWx3ZWlnaHRzX21pc3NpbmcgPC0gc3VtKCEobW9kZWx3ZWlnaHRzJEdlbmUgJWluJSBxdWVyeWdlbmVzKSkKICAjIGNoZWNrIGZvciBtaXNzaW5nIGdlbmVzCiAgaWYobW9kZWx3ZWlnaHRzX21pc3NpbmcgPiAwKXsKICAgIHByaW50KHBhc3RlMCgnV2FybmluZzogJywgbW9kZWx3ZWlnaHRzX21pc3NpbmcsICcgZ2VuZXMgZnJvbSBOTUYgbW9kZWxzIGFyZSBtaXNzaW5nIGZyb20gcXVlcnkgZGF0YXNldCcpKQogIH0KICAKICAjIGZpbHRlciBtb2RlbCB3ZWlnaHRzCiAgbW9kZWx3ZWlnaHRzIDwtIG1vZGVsd2VpZ2h0cyAlPiUgZmlsdGVyKEdlbmUgJWluJSBxdWVyeWdlbmVzKQogIG1vZGVsd2VpZ2h0c19tYXQgPC0gbW9kZWx3ZWlnaHRzICU+JSBjb2x1bW5fdG9fcm93bmFtZXMoJ0dlbmUnKSAlPiUgZGF0YS5tYXRyaXgoKQogIAogICMgbXVsdGlwbHkgcXVlcnkgYnkgTk1GIGxhc3NvIHdlaWdodHMKICBzY29yZWQgPC0gKHQocXVlcnlbbW9kZWx3ZWlnaHRzJEdlbmUsXSkgJSolIG1vZGVsd2VpZ2h0c19tYXQpICU+JSBkYXRhLm1hdHJpeCgpIAogIGlmKHNjYWxlID09IFRSVUUpewogICAgc2NvcmVkIDwtIHNjYWxlKHNjb3JlZCkKICB9CiAgc2NvcmVkIDwtIHNjb3JlZCAlPiUgYXMuZGF0YS5mcmFtZSgpICU+JSByb3duYW1lc190b19jb2x1bW4oc2FtcGxlSUQpIAogIAogIHJldHVybihzY29yZWQpCn0KYGBgCgoKYGBge3J9Cm1vZGVsd2VpZ2h0cyA8LSByZWFkX2NzdigiTk1GX0xhc3NvX01vZGVsV2VpZ2h0cy5jc3YiKQptb2RlbHdlaWdodHMKYGBgCgpgYGB7cn0KcHNldWRvYnVsa192c3QgPC0gcmVhZFJEUygiLi4vQkFMTGJ1bGtfRGVjb252b2x1dGlvbi9CQUxMXzg5cHRfcHNldWRvYnVsa192c3QucmRzIikKIyBjYWxjdWxhdGUgTk1GIHNjb3JlcyBmcm9tIHZzdC1ub3JtYWxpemVkIGRhdGEKcHNldWRvYnVsa192c3RfTk1Gc2NvcmVzIDwtIGNhbGN1bGF0ZV9OTUZzY29yZXMocHNldWRvYnVsa192c3RAYXNzYXlzJFJOQUBkYXRhLCBtb2RlbHdlaWdodHMsIHNjYWxlID0gVCwgc2FtcGxlSUQgPSAnUGF0aWVudCcpCnBzZXVkb2J1bGtfdnN0X05NRnNjb3Jlc1sxOjEwLDE6MTBdCmBgYAoKKipUcmFpbmluZyByZXN1bHRzIC0gcHNldWRvYnVsayoqCgpgYGB7ciwgZmlnLmhlaWdodCA9IDUsIGZpZy53aWR0aCA9IDEyfQpOTUZfY29tcGFyZSA8LSBOTUZfcHRzY29yZXMgJT4lIAogIGxlZnRfam9pbihOTUZjb252ZXJ0KSAlPiUgCiAgbGVmdF9qb2luKHBzZXVkb2J1bGtfdnN0X05NRnNjb3JlcyAlPiUgcGl2b3RfbG9uZ2VyKC1QYXRpZW50LCBuYW1lc190byA9ICdOTUZuYW1lZCcsIHZhbHVlc190byA9ICdwcmVkTk1GJykpIAoKTk1GX2NvbXBhcmUgJT4lIAogIG11dGF0ZShOTUZuYW1lZCA9IGZhY3RvcihOTUZuYW1lZCwgbGV2ZWxzID0gTk1GbmFtZWRfbGV2ZWxzKSkgJT4lIAogIGdncGxvdChhZXMoeCA9IHByZWROTUYsIHkgPSBOTUZzY29yZSkpICsgCiAgZ2VvbV9wb2ludCgpICsgZ2VvbV9zbW9vdGgobWV0aG9kID0gJ2xtJykgKyAKICBmYWNldF93cmFwKC5+Tk1GbmFtZWQsIHNjYWxlcyA9ICdmcmVlJykgKyAKICB0aGVtZV9wdWJyKCkgKyBzdGF0X2NvcigpCmBgYAoKCgoKIyMjIFRydWUgQnVsayBSTkFzZXEgInZhbGlkYXRpb24iCgpgYGB7cn0KYnVsa1JOQV9jb3VudHMgPC0gZGF0YS50YWJsZTo6ZnJlYWQoIi4uL0JBTExidWxrX0RlY29udm9sdXRpb24vQkFMTF9idWxrUk5BX2RhdGEvQkFMTF9CdWxrUk5Bc2VxX3N1YnNldGdlbmVfcmF3Y291bnRzLnR4dCIpICU+JSAKICBjb2x1bW5fdG9fcm93bmFtZXMoJ0dlbmUnKSAlPiUgZGF0YS5tYXRyaXgoKQpidWxrUk5BX2NvdW50c1sxOjEwLDE6MTBdCmBgYAoKYGBge3J9CmxpYnJhcnkoREVTZXEyKQpidWxrUk5BX2RkcyA8LSBERVNlcURhdGFTZXRGcm9tTWF0cml4KGJ1bGtSTkFfY291bnRzW3Jvd1N1bXMoYnVsa1JOQV9jb3VudHMpID49IDEwLF0sIAogICAgICAgICAgICAgICAgICAgICAgIGNvbERhdGEgPSBkYXRhLmZyYW1lKCdQYXRpZW50JyA9IGNvbG5hbWVzKGJ1bGtSTkFfY291bnRzKSkgJT4lIGNvbHVtbl90b19yb3duYW1lcygnUGF0aWVudCcpLCAKICAgICAgICAgICAgICAgICAgICAgICBkZXNpZ24gPSB+MSkKYnVsa1JOQV92c3QgPC0gYXNzYXkodnN0KGJ1bGtSTkFfZGRzKSkKcm0oYnVsa1JOQV9jb3VudHMsIGJ1bGtSTkFfZGRzKQoKYnVsa1JOQV92c3RbMToxMCwxOjEwXQpgYGAKCmBgYHtyfQojIGNhbGN1bGF0ZSBOTUYgc2NvcmVzIGZyb20gdnN0LW5vcm1hbGl6ZWQgZGF0YQpidWxrUk5BX3ZzdF9OTUZzY29yZXMgPC0gY2FsY3VsYXRlX05NRnNjb3JlcyhidWxrUk5BX3ZzdCwgbW9kZWx3ZWlnaHRzLCBzY2FsZSA9IFQsIHNhbXBsZUlEID0gJ1BhdGllbnQnKQpidWxrUk5BX3ZzdF9OTUZzY29yZXNbMToxMCwxOjEwXQpgYGAKCioqVmFsaWRhdGlvbiByZXN1bHRzIC0gbWF0Y2hlZCBidWxrUk5Bc2VxKioKCmBgYHtyfQpOTUZfcHRzY29yZXNfY29udmVydGVkIDwtIE5NRl9wdHNjb3JlcyAlPiUgCiAgbGVmdF9qb2luKHJlYWRfZGVsaW0oIi4uL0JBTExfbWV0YWRhdGFfMjAyMzAxMDUudHh0IiwgZGVsaW0gPSAnXHQnKSAlPiUgc2VsZWN0KFBhdGllbnQgPSBEaXJlY3RvcnksIFNhbXBsZSwgSUQsIFRCKSkgCgpOTUZfcHRzY29yZXNfY29tcGFyZUJ1bGtSTkEgPC0gTk1GX3B0c2NvcmVzX2NvbnZlcnRlZCAlPiUgCiAgbGVmdF9qb2luKE5NRmNvbnZlcnQpICU+JSAKICBsZWZ0X2pvaW4oYnVsa1JOQV92c3RfTk1Gc2NvcmVzICU+JSBwaXZvdF9sb25nZXIoLVBhdGllbnQsIG5hbWVzX3RvID0gJ05NRm5hbWVkJywgdmFsdWVzX3RvID0gJ3ByZWROTUYnKSAlPiUgCiAgICAgICAgICAgICAgZHBseXI6OnJlbmFtZShJRCA9IFBhdGllbnQpKSAKCk5NRl9wdHNjb3Jlc19jb21wYXJlQnVsa1JOQQpgYGAKCgpgYGB7ciwgZmlnLmhlaWdodCA9IDMuNSwgZmlnLndpZHRoID0gMTJ9Ck5NRl9wdHNjb3Jlc19jb21wYXJlQnVsa1JOQSAlPiUgCiAgbXV0YXRlKE5NRm5hbWVkID0gZmFjdG9yKE5NRm5hbWVkLCBOTUZuYW1lZF9sZXZlbHMpKSAlPiUgCiAgZ2dwbG90KGFlcyh4ID0gcHJlZE5NRiwgeSA9IE5NRnNjb3JlKSkgKyAKICBnZW9tX3BvaW50KCkgKyBnZW9tX3Ntb290aChtZXRob2QgPSAnbG0nKSArIAogIGZhY2V0X3dyYXAoLn5OTUZuYW1lZCwgc2NhbGVzID0gJ2ZyZWUnLCBuY29sID0gNSkgKyAKICB0aGVtZV9wdWJyKCkgKyBzdGF0X2NvcigpICsgCiAgeWxhYignTk1GIExpbmVhZ2UgU2NvcmUgKHNjUk5BIGNvbXBvc2l0aW9uKScpICsgeGxhYignUHJlZGljdGVkIE5NRiBTY29yZSAobWF0Y2hlZCBidWxrIFJOQS1zZXEpJykKYGBgCgoKCmBgYHtyLCBmaWcuaGVpZ2h0ID0gNiwgZmlnLndpZHRoID0gNH0KTk1GX3B0c2NvcmVzX2NvbXBhcmVCdWxrUk5BICU+JSAKICBtdXRhdGUoTk1GbmFtZWQgPSBmYWN0b3IoTk1GbmFtZWQsIE5NRm5hbWVkX2xldmVscykpICU+JSAKICBnZ3Bsb3QoYWVzKHggPSBwcmVkTk1GLCB5ID0gTk1Gc2NvcmUpKSArIAogIGdlb21fcG9pbnQoKSArIGdlb21fc21vb3RoKG1ldGhvZCA9ICdsbScpICsgCiAgZmFjZXRfd3JhcCgufk5NRm5hbWVkLCBzY2FsZXMgPSAnZnJlZScsIG5jb2wgPSAyKSArIAogIHRoZW1lX3B1YnIoKSArIHN0YXRfY29yKCkgKyAKICB5bGFiKCdOTUYgTGluZWFnZSBTY29yZSAoc2NSTkEgY29tcG9zaXRpb24pJykgKyB4bGFiKCdQcmVkaWN0ZWQgTk1GIFNjb3JlIChtYXRjaGVkIGJ1bGsgUk5BLXNlcSknKQpgYGAKCk5vdCBiYWQhIQpTYXZlIGJ1bGsgUk5Bc2VxIE5NRiBzY29yZXM6IAoKYGBge3J9CmJ1bGtSTkFfdnN0X05NRnNjb3JlcyAlPiUgd3JpdGVfY3N2KCdCdWxrMjA0Nl9OTUZyZWdyZXNzaW9uX0xpbmVhZ2VTY29yZXMuY3N2JykKYnVsa1JOQV92c3RfTk1Gc2NvcmVzCmBgYAoKCmBgYHtyfQpvdXRwdXQgJT4lIAogIGxlZnRfam9pbihOTUZjb252ZXJ0KSAlPiUKICAjbXV0YXRlKE5NRiA9IGZhY3RvcihOTUYsIGxldmVscyA9IGMoJ05NRjEnLCAnTk1GMicsICdOTUYzJywgJ05NRjQnLCAnTk1GNScsICdOTUY2JywgJ05NRjcnLCAnTk1GOCcsICdOTUY5JywgJ05NRjEwJykpKSAlPiUgCiAgZmlsdGVyKGZlYXR1cmVzID09ICdBbnlDb3JyX0xpbkRFX0JEZXZERV9GRFIwMScsIGxhbWJkYSA9PSAnbGFtYmRhLjFzZScpICU+JSAKICBnZ3Bsb3QoYWVzKHggPSBOTUZuYW1lZCwgeSA9IHBlYXJzb24sIGZpbGwgPSBOTUZuYW1lZCkpICsgCiAgdGhlbWVfcHVicihsZWdlbmQgPSAnbm9uZScpICsgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gMC41LCBsdHkgPSAyKSArIGdlb21faGxpbmUoeWludGVyY2VwdCA9IDAuNzUsIGx0eSA9IDIpICsgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gMC45LCBsdHkgPSAyKSArIAogIGdlb21fYm94cGxvdChvdXRsaWVyLnNpemUgPSAwKSArIGdnYmVlc3dhcm06Omdlb21fcXVhc2lyYW5kb20oc2l6ZSA9IDEsIGFscGhhID0gMC43KSArIAogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gOTAsIGhqdXN0ID0gMSwgdmp1c3QgPSAwLjUpKSAKICAKYGBgCgoKYGBge3J9Cm91dHB1dCAlPiUgCiAgbGVmdF9qb2luKE5NRmNvbnZlcnQpICU+JQogICNtdXRhdGUoTk1GID0gZmFjdG9yKE5NRiwgbGV2ZWxzID0gYygnTk1GMScsICdOTUYyJywgJ05NRjMnLCAnTk1GNCcsICdOTUY1JywgJ05NRjYnLCAnTk1GNycsICdOTUY4JywgJ05NRjknLCAnTk1GMTAnKSkpICU+JSAKICBmaWx0ZXIoZmVhdHVyZXMgPT0gJ0FueUNvcnJfTGluREVfQkRldkRFX0ZEUjAxJywgbGFtYmRhID09ICdsYW1iZGEuMXNlJykgJT4lIAogIGdncGxvdChhZXMoeCA9IHJlb3JkZXIoTk1GbmFtZWQsIC1wZWFyc29uKSwgeSA9IHBlYXJzb24sIGZpbGwgPSBOTUZuYW1lZCkpICsgCiAgdGhlbWVfcHVicihsZWdlbmQgPSAnbm9uZScpICsgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gMC41LCBsdHkgPSAyKSArIGdlb21faGxpbmUoeWludGVyY2VwdCA9IDAuNzUsIGx0eSA9IDIpICsgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gMC45LCBsdHkgPSAyKSArIAogIGdlb21fYm94cGxvdChvdXRsaWVyLnNpemUgPSAwKSArIGdnYmVlc3dhcm06Omdlb21fcXVhc2lyYW5kb20oc2l6ZSA9IDEsIGFscGhhID0gMC43KSArIAogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gOTAsIGhqdXN0ID0gMSwgdmp1c3QgPSAwLjUpKSAKICAKYGBgCgpgYGB7cn0KTk1GX3B0c2NvcmVzX2NvbXBhcmVCdWxrUk5BICU+JSBkcm9wX25hKCkgJT4lIAogIGdyb3VwX2J5KCkKYGBgCgoKCgoKCiMjIHF1aWNrbHkgZXZhbHVhdGUgb24gY29yZCBibG9vZCBzb3J0ZWQKCmBgYHtyfQpjYl9mcmFjdGlvbnMgPSBkYXRhLnRhYmxlOjpmcmVhZCgnLi4vLi4vLi4vLi4vLi4vLi4vLi4vRG9ybWFuY3kvSFNDX2FuYWx5c2lzL0hTQ19ieU9udG9nZW55L0RpY2tsYWJfc29ydGVkRnJhY3Rpb25zX0JhdGNoNF9DQl9IaWVyYXJjaHlfdnN0LmNzdicpCmNiX2ZyYWN0aW9ucyA8LSBjYl9mcmFjdGlvbnMgJT4lIGNvbHVtbl90b19yb3duYW1lcygnR2VuZScpICU+JSBkYXRhLm1hdHJpeCgpCmNiX2ZyYWN0aW9ucyAlPiUgZGltKCkKYGBgCgoKYGBge3J9CkNCZnJhY3Rpb25zX3ZzdF9OTUZzY29yZXMgPC0gY2FsY3VsYXRlX05NRnNjb3JlcyhjYl9mcmFjdGlvbnMsIG1vZGVsd2VpZ2h0cywgc2NhbGUgPSBULCBzYW1wbGVJRCA9ICdTYW1wbGUnKQpDQmZyYWN0aW9uc192c3RfTk1Gc2NvcmVzCmBgYAoKYGBge3IsIGZpZy5oZWlnaHQgPSA4LCBmaWcud2lkdGggPSAxMH0KQ0JmcmFjdGlvbnNfdnN0X05NRnNjb3JlcyAlPiUgCiAgcGl2b3RfbG9uZ2VyKC1TYW1wbGUsIG5hbWVzX3RvID0gJ05NRnNpZycsIHZhbHVlc190byA9ICdTY29yZScpICU+JSAKICBtdXRhdGUoTk1Gc2lnID0gZmFjdG9yKE5NRnNpZywgbGV2ZWxzID0gTk1GbmFtZWRfbGV2ZWxzKSwgCiAgICAgICAgIFBvcHVsYXRpb24gPSBTYW1wbGUgJT4lIHN0cl9yZXBsYWNlKCcuKkNCXycsJycpLAogICAgICAgICBQb3B1bGF0aW9uID0gZmFjdG9yKFBvcHVsYXRpb24sIGxldmVscyA9IGMoJ0hTQycsICdNUFAnLCAnTE1QUCcsICdDTVAnLCAnR01QJywgJ01MUElJJywgJ0Vhcmx5UHJvQicsICdQcmVQcm9CJywgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAnUHJvQicsICdQcmVCJywgJ0InLCAjJ0IxJywgJ0IyJywgJ0IzJywgJ0I0JywgJ0I1JywgJ0I2JywgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAnVCcsICdOSycsICdFcnlQJywgJ01vbm8nLCAnR3InKSkpICU+JSAKICBmaWx0ZXIoUG9wdWxhdGlvbiAhPSAnTkEnKSAlPiUgCiAgZ2dwbG90KGFlcyh4ID0gUG9wdWxhdGlvbiwgeSA9IFNjb3JlLCBmaWxsID0gUG9wdWxhdGlvbikpICsgCiAgZ2VvbV9ib3hwbG90KCkgKyBnZW9tX2ppdHRlcigpICsKICB0aGVtZV9wdWJyKGxlZ2VuZCA9ICdub25lJykgKyB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDkwLCBoanVzdCA9IDEsIHZqdXN0ID0gMC41KSkgKwogIGZhY2V0X3dyYXAoLn5OTUZzaWcsIHNjYWxlcyA9ICdmcmVlJywgbmNvbCA9IDMpCiAgCmBgYAoKYGBge3J9CkNCZnJhY3Rpb25zX3ZzdF9OTUZzY29yZXMgJT4lIAogIHBpdm90X2xvbmdlcigtU2FtcGxlLCBuYW1lc190byA9ICdOTUZzaWcnLCB2YWx1ZXNfdG8gPSAnU2NvcmUnKSAlPiUgCiAgbXV0YXRlKE5NRnNpZyA9IGZhY3RvcihOTUZzaWcsIGxldmVscyA9IE5NRm5hbWVkX2xldmVscyksIAogICAgICAgICBQb3B1bGF0aW9uID0gU2FtcGxlICU+JSBzdHJfcmVwbGFjZSgnLipDQl8nLCcnKSwKICAgICAgICAgUG9wdWxhdGlvbiA9IGZhY3RvcihQb3B1bGF0aW9uLCBsZXZlbHMgPSBjKCdIU0MnLCAnTVBQJywgJ0xNUFAnLCAnQ01QJywgJ0dNUCcsICdNTFBJSScsICdFYXJseVByb0InLCAnUHJlUHJvQicsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJ1Byb0InLCAnUHJlQicsICdCJywgIydCMScsICdCMicsICdCMycsICdCNCcsICdCNScsICdCNicsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJ1QnLCAnTksnLCAnRXJ5UCcsICdNb25vJywgJ0dyJykpKSAlPiUgCmBgYAoKCgojIyBFdmFsdWF0ZSBvbiBQaGFybWFjb3R5cGVzIAoKYGBge3J9CiMgcmVxdWlyZSBnZW5lIHN5bWJvbCBjb2x1bW4gdG8gYmUgbmFtZWQgIkdlbmUiCnJwa21fdG9fbG9nVFBNIDwtIGZ1bmN0aW9uKGRhdCl7CiAgIyBjb252ZXJ0IHRvIFRQTQogIGRhdF9UUE0gPC0gZGF0ICU+JSAKICAgIGdhdGhlcigtR2VuZSwga2V5ID0gIlNhbXBsZSIsIHZhbHVlID0gIlJQS00iKSAlPiUKICAgIGdyb3VwX2J5KFNhbXBsZSkgJT4lIAogICAgbXV0YXRlKGxvZ1RQTSA9IGxvZzFwKFJQS00gLyBzdW0oUlBLTSkgKiAxMDAwMDAwKSkgJT4lIAogICAgc2VsZWN0KC1SUEtNKSAlPiUgdW5ncm91cCgpICU+JSAKICAgIHNwcmVhZChTYW1wbGUsIGxvZ1RQTSkKICAKICByZXR1cm4oZGF0X1RQTSkKfQpgYGAKCgpgYGB7cn0KcGhhcm1hY290eXBlX2Zwa20gPC0gZGF0YS50YWJsZTo6ZnJlYWQoJ3BoYXJtYWNvdHlwZXMvcGhhcm1hY290eXBpbmdfcGVkX3JuYXNlcV9mcGttX0FMTGlkc18wODIzLmNzdicpICU+JSBzZWxlY3QoLUdlbmVJRCkgJT4lIGRwbHlyOjpyZW5hbWUoR2VuZSA9IEdlbmVOYW1lKQpwaGFybWFjb3R5cGVfZnBrbQpgYGAKCmBgYHtyfQpwaGFybWFjb3R5cGVfbG9nVFBNIDwtIHBoYXJtYWNvdHlwZV9mcGttICU+JSBycGttX3RvX2xvZ1RQTSgpCnBoYXJtYWNvdHlwZV9sb2dUUE0gPC0gcGhhcm1hY290eXBlX2xvZ1RQTSAlPiUgY29sdW1uX3RvX3Jvd25hbWVzKCdHZW5lJykgJT4lIGRhdGEubWF0cml4KCkKcGhhcm1hY290eXBlX2xvZ1RQTSAlPiUgZGltKCkKYGBgCgoKYGBge3J9CnBoYXJtYWNvdHlwZV9sb2dUUE1fc2NvcmVkIDwtIGNhbGN1bGF0ZV9OTUZzY29yZXMocGhhcm1hY290eXBlX2xvZ1RQTSwgbW9kZWx3ZWlnaHRzLCBzY2FsZSA9IFQsIHNhbXBsZUlEID0gJ1BhdGllbnQgSUQnKQpwaGFybWFjb3R5cGVfbG9nVFBNX3Njb3JlZCAlPiUgd3JpdGVfY3N2KCdwaGFybWFjb3R5cGVzL0FMTF9waGFybWFjb3R5cGVzX2xvZ1RQTV9EZXZTdGF0ZV9zY29yZXMuY3N2JykKcGhhcm1hY290eXBlX2xvZ1RQTV9zY29yZWQKYGBgCgoKYGBge3J9CnBoYXJtYWNvdHlwZXMgPC0gcmVhZF9jc3YoJ3BoYXJtYWNvdHlwZXMvQUxMX2ludml0cm9fcGhhcm1hY290eXBlcy5jc3YnKQpwaGFybWFjb3R5cGVzCmBgYAoKCmBgYHtyfQpwaGFybWFjb3R5cGVzX2NvbWJpbmVkIDwtIHBoYXJtYWNvdHlwZXMgJT4lIGlubmVyX2pvaW4ocGhhcm1hY290eXBlX2xvZ1RQTV9zY29yZWQpICU+JSBmaWx0ZXIoSW1tdW5vcGhlbm90eXBlID09ICdCJykKcGhhcm1hY290eXBlc19jb21iaW5lZApgYGAKCmBgYHtyfQpDZWxsVHlwZV9EcnVnX0NvcnIgPC0gY29yKHggPSBzZWxlY3QocGhhcm1hY290eXBlc19jb21iaW5lZCwgY29udGFpbnMoJ25vcm1hbGl6ZWQnKSksIAogICAgICAgICAgICAgICAgICAgICAgICAgIHkgPSBzZWxlY3QocGhhcm1hY290eXBlc19jb21iaW5lZCwgSFNDX01QUCwgTXllbG9pZF9Qcm9nLCBQcmVfcERDLCBFYXJseV9MeW1waG9pZCwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBQcm9fQiwgUHJlX0IsIE1hdHVyZV9CLCBUX05LLCBNb25vY3l0ZSwgRXJ5dGhyb2lkKSwgdXNlID0gJ3BhaXJ3aXNlLmNvbXBsZXRlLm9icycsIG1ldGhvZCA9ICdzcGVhcm1hbicpCkNlbGxUeXBlX0RydWdfQ29ycgpgYGAKCmBgYHtyfQpDZWxsVHlwZV9EcnVnX0NvcnIKYGBgCgpgYGB7cn0KbGlicmFyeShjb3JycGxvdCkKQ2VsbFR5cGVfRHJ1Z19Db3JyICU+JSB0KCkgJT4lIGNvcnJwbG90KCkKYGBgCgoKIyMgSmFlIEtpbSBCLUFMTCBQaCsKCmBgYHtyfQpLaW1fUGhBTExfc2FtcGxlcyA8LSBsaXN0LmZpbGVzKCdzdWJ0eXBlX3N1YmNsdXN0ZXIvS2ltMjAyM19QaF9CQUxML1JOQXNlcV9yYXdjb3VudHMvJykKS2ltX1BoQUxMX3NhbXBsZXMKYGBgCgpgYGB7cn0KS2ltX1BoQUxMX2Fubm8gPC0gcmVhZF9jc3YoJ3N1YnR5cGVfc3ViY2x1c3Rlci9LaW0yMDIzX1BoX0JBTEwvUGhfQkFMTF9DbGluaWNhbEFubm8uY3N2JykKS2ltX1BoQUxMX2Fubm8KYGBgCgpgYGB7cn0Ka2ltX3BoQUxMX2NvdW50cyA8LSBkYXRhLnRhYmxlOjpmcmVhZChwYXN0ZTAoJ3N1YnR5cGVfc3ViY2x1c3Rlci9LaW0yMDIzX1BoX0JBTEwvUk5Bc2VxX3Jhd2NvdW50cy8nLCBLaW1fUGhBTExfc2FtcGxlc1sxXSkpIApjb2xuYW1lcyhraW1fcGhBTExfY291bnRzKSA8LSBjKCdFTlNHJywgS2ltX1BoQUxMX3NhbXBsZXNbMV0gJT4lIHN0cl9yZXBsYWNlKCdfY291bnRfc3ViLnR4dCcsJycpKQoKZm9yKHBoX3NhbXAgaW4gS2ltX1BoQUxMX3NhbXBsZXNbLTFdKXsKICBwcmludChwaF9zYW1wKQogICMgTG9hZCBmaWxlIAogIHRlbXAgPC0gZGF0YS50YWJsZTo6ZnJlYWQocGFzdGUwKCdzdWJ0eXBlX3N1YmNsdXN0ZXIvS2ltMjAyM19QaF9CQUxML1JOQXNlcV9yYXdjb3VudHMvJywgcGhfc2FtcCkpCiAgY29sbmFtZXModGVtcCkgPC0gYygnRU5TRycsIHBoX3NhbXAgJT4lIHN0cl9yZXBsYWNlKCdfY291bnRfc3ViLnR4dCcsJycpKSAKICAjIG1lcmdlCiAga2ltX3BoQUxMX2NvdW50cyA8LSBraW1fcGhBTExfY291bnRzICU+JSBsZWZ0X2pvaW4odGVtcCkKfQoKa2ltX3BoQUxMX2NvdW50cwpgYGAKCmBgYHtyfQpFTlNHY29udmVydCA8LSBkYXRhLnRhYmxlOjpmcmVhZCgnLi4vLi4vLi4vLi4vLi4vLi4vLi4vQ0lCRVJTT1JUL25ld0RhdGFTZXRzX0p1bDIwMjAvcHJlcHJvY2Vzc2luZy9HUkNoMzhfdHJhbnNjcmlwdF9sZW5ndGhzLnR4dCcpCkVOU0djb252ZXJ0IDwtIEVOU0djb252ZXJ0ICU+JSBzZWxlY3QoRU5TRyA9IFYxLCBHZW5lID0gVjIpICU+JSB1bmlxdWUoKQpFTlNHY29udmVydApgYGAKCmBgYHtyfQpraW1fcGhBTExfY291bnRzIDwtIGtpbV9waEFMTF9jb3VudHMgJT4lIGlubmVyX2pvaW4oRU5TR2NvbnZlcnQpICU+JSBzZWxlY3QoLUVOU0cpICU+JSBzZWxlY3QoR2VuZSwgZXZlcnl0aGluZygpKSAlPiUgCiAgZ3JvdXBfYnkoR2VuZSkgJT4lIHN1bW1hcmlzZV9hbGwoc3VtKQpraW1fcGhBTExfY291bnRzCmBgYAoKYGBge3J9CmtpbV9waEFMTF9jb3VudHMgJT4lIHdyaXRlX2Nzdignc3VidHlwZV9zdWJjbHVzdGVyL0tpbTIwMjNfUGhfQkFMTC9LaW0yMDIzX1BoX0JBTExfUk5Bc2VxX2NvdW50cy5jc3YnKQpgYGAKCgpgYGB7cn0KbGlicmFyeShERVNlcTIpCgpraW1fcGhBTExfdnN0IDwtIERFU2VxRGF0YVNldEZyb21NYXRyaXgoa2ltX3BoQUxMX2NvdW50cyAlPiUgY29sdW1uX3RvX3Jvd25hbWVzKCdHZW5lJykgJT4lIGRhdGEubWF0cml4KCksIAogICAgICAgICAgICAgICAgICAgICAgIGNvbERhdGEgPSBkYXRhLmZyYW1lKCdQYXRpZW50JyA9IGNvbG5hbWVzKGtpbV9waEFMTF9jb3VudHMpWy0xXSkgJT4lIGNvbHVtbl90b19yb3duYW1lcygnUGF0aWVudCcpLCAKICAgICAgICAgICAgICAgICAgICAgICBkZXNpZ24gPSB+MSkgJT4lIHZzdCgpICU+JSBhc3NheSgpCnJtKGtpbV9waEFMTF9jb3VudHMpCgpraW1fcGhBTExfdnN0WzE6MTAsMToxMF0KYGBgCgpgYGB7cn0Ka2ltX3BoQUxMX3ZzdCAlPiUgYXMuZGF0YS5mcmFtZSgpICU+JSByb3duYW1lc190b19jb2x1bW4oJ0dlbmUnKSAlPiUgd3JpdGVfY3N2KCdzdWJ0eXBlX3N1YmNsdXN0ZXIvS2ltMjAyM19QaF9CQUxML0tpbTIwMjNfUGhfQkFMTF9STkFzZXFfdnN0LmNzdicpCmBgYAoKCmBgYHtyfQojIGNhbGN1bGF0ZSBOTUYgc2NvcmVzIGZyb20gdnN0LW5vcm1hbGl6ZWQgZGF0YQpraW1fcGhBTExfdnN0X05NRnNjb3JlcyA8LSBjYWxjdWxhdGVfTk1Gc2NvcmVzKGtpbV9waEFMTF92c3QsIG1vZGVsd2VpZ2h0cywgc2NhbGUgPSBULCBzYW1wbGVJRCA9ICdTYW1wbGUnKQpraW1fcGhBTExfdnN0X05NRnNjb3Jlc1sxOjEwLDE6MTBdCmBgYAoKYGBge3J9CmtpbV9waEFMTF92c3RfTk1Gc2NvcmVzICU+JSB3cml0ZV9jc3YoJ3N1YnR5cGVfc3ViY2x1c3Rlci9LaW0yMDIzX1BoX0JBTEwvS2ltMjAyM19QaF9CQUxMX0xpbmVhZ2VOTUZfU2NvcmVkLmNzdicpCmBgYAoKCiMgY29tcGFyZQoKYGBge3J9CktpbV9QaEFMTF9hbm5vICU+JSBtdXRhdGUoU2FtcGxlID0gaWZlbHNlKE1hbnVzY3JpcHRfbmFtZSAlPiUgc3RyX2RldGVjdCgnLVInKSwgcGFzdGUwKEpBTUxSLCAnX25uX00nKSwgcGFzdGUwKEpBTUxSLCAnX25uX1AnKSkpICU+JSAKICB3cml0ZV9jc3YoJ3N1YnR5cGVfc3ViY2x1c3Rlci9LaW0yMDIzX1BoX0JBTEwvS2ltMjAyM19QaF9CQUxMX2Fubm9fY2xlYW5lZC5jc3YnKQpgYGAKCgpgYGB7cn0KS2ltX1BoQUxMX2Fubm9fTGluZWFnZVNjb3JlZCA8LSBLaW1fUGhBTExfYW5ubyAlPiUgbXV0YXRlKFNhbXBsZSA9IGlmZWxzZShNYW51c2NyaXB0X25hbWUgJT4lIHN0cl9kZXRlY3QoJy1SJyksIHBhc3RlMChKQU1MUiwgJ19ubl9NJyksIHBhc3RlMChKQU1MUiwgJ19ubl9QJykpKSAlPiUgCiAgc2VsZWN0KFNhbXBsZSwgV0JDLCBBZ2VfYXRfZHgsIHN1YnR5cGUsIFN1Ymdyb3VwKSAlPiUgCiAgaW5uZXJfam9pbihraW1fcGhBTExfdnN0X05NRnNjb3JlcykKCktpbV9QaEFMTF9hbm5vX0xpbmVhZ2VTY29yZWQKYGBgCgpgYGB7cn0KS2ltX1BoQUxMX2Fubm8gJT4lIHB1bGwoQWdlX2F0X2R4KSAlPiUgc3VtbWFyeSgpCmBgYAoKCmBgYHtyLCBmaWcuaGVpZ2h0ID0gNSwgZmlnLndpZHRoID0gMTJ9CktpbV9QaEFMTF9hbm5vX0xpbmVhZ2VTY29yZWQgJT4lIAogIHNlbGVjdCgtU2FtcGxlLCAtV0JDLCAtQWdlX2F0X2R4LCAtc3VidHlwZSkgJT4lIAogIHBpdm90X2xvbmdlcigtU3ViZ3JvdXApICU+JSAKICBnZ3Bsb3QoYWVzKHggPSBTdWJncm91cCwgeSA9IHZhbHVlLCBmaWxsID0gU3ViZ3JvdXApKSArIAogIGdlb21fYm94cGxvdCgpICsgZ2diZWVzd2FybTo6Z2VvbV9xdWFzaXJhbmRvbSgpICsKICB0aGVtZV9wdWJyKGxlZ2VuZCA9ICdub25lJykgKyB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDkwLCBoanVzdCA9IDEsIHZqdXN0ID0gMC41KSkgKwogIGZhY2V0X3dyYXAoLn5uYW1lLCBzY2FsZXMgPSAnZnJlZScsIG5jb2wgPSA1KSArIHN0YXRfY29tcGFyZV9tZWFucygpCmBgYAoKCgoKCg==